/*	@(#)doprnt.c	2.1 	*/
/*LINTLIBRARY*/
/*
 *	_doprnt: common code for printf, fprintf, sprintf
 */

#include <stdio.h>
#include <ctype.h>
#include <varargs.h>
#include <values.h>
#include "print.h"	/* parameters & macros for doprnt */

/*
 * define memcpy() to return a "char *" rather than default to int.
 * insidious m68k bug fix.  bjp
 */
extern char *memcpy();

#define PUT(p, n)	if (n == 1) (void) putc(*p, iop); \
			else if (iop->_file == _NFILE) \
			iop->_ptr = (unsigned char *) \
				memcpy((char *)iop->_ptr, p, n) +n; \
			else (void) fwrite(p, 1, n, iop)

/*
 *	C-Library routines for floating conversion
 */
extern char *fcvt(), *ecvt();

extern int strlen(), fwrite();

int
_doprnt(format, args, iop)
register char	*format;
va_list	args;
register FILE	*iop;
{
	/* This variable counts output characters. */
	int	count = 0;

	/* Starting and ending points for value to be printed */
	register char	*bp, *p;

	/* Field width and precision */
	int	width, prec;

	/* Format code */
	register int	fcode;

	/* Number of padding zeroes required on the left and right */
	int	lzero, rzero;

	/* Flags - nonzero if corresponding character is in format */
	int	length;		/* l */
	int	fplus;		/* + */
	int	fminus;		/* - */
	int	fblank;		/* blank */
	int	fsharp;		/* # */
	int	flzero;		/* obsolescent spec: asked for leading zeroes */

	/* Values are developed in this buffer */
	char	buf[max(MAXDIGS, 1+max(MAXFCVT+MAXEXP, MAXECVT))];

	/* Pointer to sign, "0x", "0X", or empty */
	char	*prefix;

	/* Exponent or empty */
	char	*suffix;

	/* Buffer to create exponent */
	char	expbuf[MAXESIZ + 1];

	/* The value being converted, if integer */
	long	val;

	/* The value being converted, if real */
	double	dval;

	/* Output values from fcvt and ecvt */
	int	decpt, sign;

	/* Pointer to a translate table for digits of whatever radix */
	char	*tab;

	/* Work variables */
	int	k, n, hradix, lowbit;

	expbuf[MAXESIZ] = '\0';

	/*
	 *	The main loop -- this loop goes through one iteration
	 *	for each string of ordinary characters or format specification.
	 */
	for ( ; ; ) {
		for (bp = format;
		    (fcode = *format) != '\0' && fcode != '%'; format++)
			;
		if (n = format - bp) { /* ordinary non-% characters */
			count += n;
			PUT(bp, n);
		}
		if (fcode == '\0') /* end of format; normal return */
			return (ferror(iop) ? EOF : count);
		/*
		 *	% has been found.
		 *	First, parse the format specification.
		 */
		fplus = fminus = fblank = fsharp = flzero = 0;
		for ( ; ; ) { /* Scan the <flags> */
			switch (fcode = *++format) {
			case '+':
				fplus++;
				continue;
			case '-':
				fminus++;
				continue;
			case ' ':
				fblank++;
				continue;
			case '#':
				fsharp++;
				continue;
			}
			break;
		}

		/* Scan the field width */
		if (fcode == '*') {
			width = va_arg(args, int);
			if (width < 0) {
				width = -width;
				fminus++;
			}
			format++;
		} else {
			if (fcode == '0') /* obsolescent spec. */
				flzero++; /* pad with leading zeros */
			for (width = 0; isdigit(fcode = *format); format++)
				width = width * 10 + tonumber(fcode);
		}

		/* Scan the precision */
		if (*format != '.')
			prec = -1;
		else if (*++format == '*') { /* '*' instead of digits? */
			prec = va_arg(args, int);
			format++;
		} else
			for (prec = 0; isdigit(fcode = *format); format++)
				prec = prec * 10 + tonumber(fcode);

		/* Scan the length modifier */
		length = 0;
		switch (*format) {
		case 'l':
			length++;
			/* No break */
		case 'h':
			format++;
		}

		/*
		 *	The character addressed by format must be
		 *	the format letter -- there is nothing
		 *	left for it to be.
		 *
		 *	The status of the +, -, #, and blank
		 *	flags are reflected in the variables
		 *	"fplus", "fminus", "fsharp", and
		 *	"fblank".  "width" and "prec" contain
		 *	numbers corresponding to the digit
		 *	strings before and after the decimal
		 *	point, respectively. If there was no
		 *	decimal point, "prec" is -1.
		 *
		 *	The following switch sets things up
		 *	for printing.  What ultimately gets
		 *	printed will be padding blanks, a
		 *	prefix, left padding zeroes, a value,
		 *	right padding zeroes, a suffix, and
		 *	more padding blanks.  Padding blanks
		 *	will not appear simultaneously on both
		 *	the left and the right.  Each case in
		 *	this switch will compute the value, and
		 *	leave in several variables the informa-
		 *	tion necessary to construct what is to
		 *	be printed.  
		 *
		 *	The prefix is a sign, a blank, "0x",
		 *	"0X", or null, and is addressed by
		 *	"prefix".
		 *
		 *	The suffix is either null or an
		 *	exponent, and is addressed by "suffix".
		 *
		 *	The value to be printed starts at "bp"
		 *	and continues up to and not including
		 *	"p".
		 *
		 *	"lzero" and "rzero" will contain the
		 *	number of padding zeroes required on
		 *	the left and right, respectively.  If
		 *	either of these variables is negative,
		 *	it will be treated as if it were zero.
		 *
		 *	The number of padding blanks, and
		 *	whether they go on the left or the
		 *	right, will be computed on exit from
		 *	the switch.
		 */
		
		prefix = "";
		suffix = &expbuf[MAXESIZ];
		lzero = rzero = 0;

		switch (fcode = *format++) {

		/*
		 *	fixed point representations
		 *
		 *	"hradix" is half the radix for the
		 *	conversion. Conversion is unsigned
		 *	unless fcode is 'd'. HIBITL is 100...000
		 *	binary, and is equal to	the maximum
		 *	negative number.
		 *	We assume a 2's complement machine
		 */

		case 'd':
		case 'u':
			hradix = 10/2;
			goto fixed;

		case 'o':
			hradix = 8/2;
			goto fixed;

		case 'X':
		case 'x':
			hradix = 16/2;

		fixed:
			/* Establish default precision */
			if (prec < 0)
				if(flzero && width > 0) /* take care of */
					prec = width;	/* obsolescent spec */
				else
					prec = 1;

			/* Fetch the argument to be printed */
			if(length)
				val = va_arg(args, long);
			else if(fcode == 'd')
				val = va_arg(args, int);
			else
				val = va_arg(args, unsigned);

			/* If signed conversion, make sign */
			if(fcode == 'd') {
				if(val < 0) {
					prefix = "-";
					/*
					 * Negate, checking in
					 * advance for possible
					 * overflow.
					 */
					if(val != HIBITL)
						val = -val;
				} else if (fplus)
					prefix = "+";
				else if (fblank)
					prefix = " ";
			}

			/* Set translate table for digits */
			tab = (fcode == 'X') ?
			    "0123456789ABCDEF" : "0123456789abcdef";

			/* Develop the digits of the value */
			p = bp = buf + MAXDIGS;
			while (val != 0) {
				lowbit = val & 1;
				val = (val >> 1) & ~HIBITL;
				*--bp = tab[val % hradix * 2 + lowbit];
				val /= hradix;
			}

			/* Calculate padding zero requirement */
			lzero = bp - p + prec;

			/* Handle the # flag */
			if (fsharp && bp != p)
				switch (fcode) {
				case 'o':
					if (lzero < 1)
						lzero = 1;
					break;
				case 'x':
					prefix = "0x";
					break;
				case 'X':
					prefix = "0X";
					break;
				}

			break;

		case 'E':
		case 'e':
			/*
			 * E-format.  The general strategy
			 * here is fairly easy: we take
			 * what ecvt gives us and re-format it.
			 */

			/* Establish default precision */
			if (prec < 0)
				prec = 6;

			/* Fetch the value */
			dval = va_arg(args, double);

			/* Develop the mantissa */
			bp = ecvt(dval, min(prec + 1, MAXECVT), &decpt, &sign);

			/* Determine the prefix */
		e_merge:
			if (sign)
				prefix = "-";
			else if (fplus)
				prefix = "+";
			else if (fblank)
				prefix = " ";

			/* Place the first digit in the buffer*/
			p = &buf[0];
			*p++ = (*bp != '\0') ? *bp++ : '0';

			/* Put in a decimal point if needed */
			if (prec != 0 || fsharp)
				*p++ = '.';

			/* Create the rest of the mantissa */
			for (rzero = prec; rzero > 0 && *bp != '\0'; --rzero)
				*p++ = *bp++;

			bp = &buf[0];

			/* Create the exponent */
			if (dval != 0) {
				n = decpt - 1;
				if (n < 0)
				    n = -n;
				for ( ; n != 0; n /= 10)
					*--suffix = todigit(n % 10);
			}

			/* Prepend leading zeroes to the exponent */
			while (suffix > &expbuf[MAXESIZ - 2])
				*--suffix = '0';

			/* Put in the exponent sign */
			*--suffix = (decpt > 0 || dval == 0) ? '+' : '-';

			/* Put in the e */
			*--suffix = isupper(fcode) ? 'E'  : 'e';

			break;

		case 'f':
			/*
			 * F-format floating point.  This is a
			 * good deal less simple than E-format.
			 * The overall strategy will be to call
			 * fcvt, reformat its result into buf,
			 * and calculate how many trailing
			 * zeroes will be required.  There will
			 * never be any leading zeroes needed.
			 */

			/* Establish default precision */
			if (prec < 0)
				prec = 6;

			/* Fetch the value */
			dval = va_arg(args, double);

			/* Do the conversion */
			bp = fcvt(dval, min(prec, MAXFCVT), &decpt, &sign);

			/* Determine the prefix */
		f_merge:
			if (sign && decpt > -prec && *bp != '0')
				prefix = "-";
			else if (fplus)
				prefix = "+";
			else if (fblank)
				prefix = " ";

			/* Initialize buffer pointer */
			p = &buf[0];

			/* Emit the digits before the decimal point */
			n = decpt;
			k = 0;
			do {
				*p++ = (n <= 0 || *bp == '\0' || k >= MAXFSIG) ?
				    '0' : (k++, *bp++);
			} while (--n > 0);

			/* Decide whether we need a decimal point */
			if (fsharp || prec > 0)
				*p++ = '.';

			/* Digits (if any) after the decimal point */
			n = min(prec, MAXFCVT);
			rzero = prec - n;
			while (--n >= 0)
				*p++ = (++decpt <= 0 || *bp == '\0' ||
				    k >= MAXFSIG) ? '0' : (k++, *bp++);

			bp = &buf[0];

			break;

		case 'G':
		case 'g':
			/*
			 * g-format.  We play around a bit
			 * and then jump into e or f, as needed.
			 */
		
			/* Establish default precision */
			if (prec < 0)
				prec = 6; 
			else if (prec == 0)  /* if precision (max significant */
				prec = 1;    /* digits) is zero - force to 1 */

			/* Fetch the value */
			dval = va_arg(args, double);

			/* Do the conversion */
			bp = ecvt(dval, min(prec, MAXECVT), &decpt, &sign);
			if (dval == 0)
				decpt = 1;

			k = prec;
			if (!fsharp) {
				n = strlen(bp);
				if (n < k)
					k = n;
				while (k >= 1 && bp[k-1] == '0')
					--k;
			}
				
			if (decpt < -3 || decpt > prec) {
				prec = k - 1;
				goto e_merge;
			}
			prec = k - decpt;
			goto f_merge;

	/*	case '%':	*/
		default:
			buf[0] = fcode;
			goto c_merge;

		case 'c':
			buf[0] = va_arg(args, int);
		c_merge:
			p = (bp = &buf[0]) + 1;
			break;

		case 's':
			p = bp = va_arg(args, char *);
			if (prec < 0)
				p += strlen(bp);
			else { /* a strnlen function would  be useful here! */
				while (*p++ != '\0' && --prec >= 0)
					;
				--p;
			}
			break;

		case '\0': /* unexpected end of format; return error */
			return (EOF);

		}

		/* Calculate number of padding blanks */
		if (lzero < 0)
			lzero = 0;
		if (rzero < 0)
			rzero = 0;
		k = (n = p - bp) + lzero + rzero +
		    (prefix[0] == '\0' ? 0 : prefix[1] == '\0' ? 1 : 2) +
		    (&expbuf[MAXESIZ] - suffix);
		count += (width > k) ? width : k;

		/* Blanks on left if required */
		if (!fminus)
			while (--width >= k)
				(void) putc(' ', iop);

		/* Prefix, if any */
		while (*prefix != '\0')
			(void) putc(*prefix++, iop);

		/* Zeroes on the left */
		while (--lzero >= 0)
			(void) putc('0', iop);
		
		/* The value itself */
		if (n > 0)
			PUT(bp, n);

		/* Zeroes on the right */
		while (--rzero >= 0)
			(void) putc('0', iop);

		/* The suffix */
		while (*suffix != '\0')
			(void) putc(*suffix++, iop);

		/* Blanks on the right if required */
		while (--width >= k)
			(void) putc(' ', iop);
	}
}
