
/* this is based on the DOPRNT distributed by MUN, but extensively
   hacked by jrd.  It's no longer doprnt, rather it's something general,
   purpose, suitable for underlying various printf variants etc */

#include	<varargs.h>
#include	<ctype.h>

#define	SZ_NORM		0
#define	SZ_SHORT	1
#define	SZ_LONG		2

/* the formatter for flona */
char * _flofmt(buf, flags, value, precision, field_width)
char * buf;
int flags;
double value;
int precision, field_width;
{
  char * orig_buf = buf;
  long int_part;
  int place_number;
  double place;
  char digit[32];
  int idx, ndigits;
  int ten = 10;
  int zero = 0;

/* sanity check field qualifiers */
  if (field_width < 18)		/* 17 digits is as much double precision as we can get */
	field_width = 18;
  if (precision > field_width)
	precision = field_width;
  if (precision > 18)
	precision = 18;

  if (value < zero)
	{
	*buf++ = '-';
	value = -value;
	}
  int_part = (long )value;
  value = value - (double )int_part;
  for (idx = 0 ; (idx == 0) || (int_part > 0) ; idx++)
	{
	digit[idx] = int_part % ten;
/* kludge til we get doubles accurate... */
	if ((digit[idx] > 9) || (digit[idx] < 0))
		digit[idx] = 0;
	int_part = int_part / ten;
	}
  for ( ; idx > 0 ; )
	*buf++ = digit[--idx] + 48;

/* now do the decimal places */
  for (idx = 0 ; idx < 20 ; idx++)
	digit[idx] = 0;
  for (idx = 0, ndigits = 0 ; (idx < 20) && (value > 0.0) ; idx++)
	{
	value = value * ten;
	int_part = (long )value;
	value = value - (double )int_part;
	digit[idx] = int_part;
/* kludge til we get doubles accurate... */
	if ((digit[idx] > 9) || (digit[idx] < 0))
		{
		digit[idx] = 0;
		break;
		}
	if (int_part > 0) ndigits = idx + 1;
	}
  if (precision > -1)			/* he say how many places? */ 
	ndigits = precision;		/* yes, use his */
  if (ndigits > 0)			/* were there any? */
	{
	*buf++ = '.';
	for (idx = 0 ; idx < ndigits ; idx++)
		{
		*buf++ = digit[idx] + 48;
		}
	}
  *buf = '\0';
  return(orig_buf);  
}


/* and here's the main loop */
void _printf_guts(template, args, fun, funargs)
char	*template;
va_list	args;
void	(* fun)();	/* foo(char, args) */
long	**funargs;
{
  register char		c;
  register char		*s;
  register char		*p;
  register unsigned long	lnum;
  double		dnum;
  register int		tmp;
  register int		field;
  register int		precision;
  int			hash;
  int			pad;
  int			adjust;
  int			signfmt;
  int			size;
  int			upper;
  int			neg;
  char			numbuf[36];

  if (!template)
	return;

  while (c = *template++) 
	{
	if (c == '%') 
		{
		c = *template++;
		if (c == '#')
			hash = 1, c = *template++;
		    else
			hash = 0;

		signfmt = 0;
		adjust = 0;
		while (1) 
			{
			if (c == '-')
				adjust = 1, c = *template++;
			else if (c == '+')
				signfmt = 1, c = *template++;
			else if (c == ' ') 
				{
				if (!signfmt)	/* + take presidence */
					signfmt = -1;
				c = *template++;
				}
			    else
				break;
			}

		pad = ' ';
		field = 0;
		if (c == '0')
			pad = '0', c = *template++;
		if (isdigit(c)) 
			{
			while (isdigit(c)) 
				{
				field = field * 10 + c - '0';
				c = *template++;
				}
			}
		    else
		if (c == '*') 
			{
			field = va_arg(args, int), c = *template++;
			if (field < 0) 
				{
				field = -field;
				adjust = 1;
				}
			}

		precision = 32000;
		if (c == '.') 
			{
			c = *template++;
			if (c == '*') 
				{
				precision = va_arg(args, int);
				c = *template++;
				}
			    else 
				{
				precision = 0;
				while (isdigit(c)) 
					{
					precision = precision * 10
						+ c - '0';
					c = *template++;
					}
				}
			}

		size = SZ_NORM;
		if (c == 'l')
			size = SZ_LONG, c = *template++;
		else if (c == 'h')
			size = SZ_SHORT, c = *template++;
		/* This is dodgy - does X mean long hex or
		 * use uppercase hex digits?  Does both right
		 * now so becareful...
		 */
		if (isupper(c))
			upper = 1, size = SZ_LONG, c = tolower(c);
		    else
			upper = 0;

		if (c == 'd' || c == 'o' || c == 'x' || c == 'u')
			switch (size) 
				{
			case SZ_NORM:
			case SZ_SHORT:
				/* What kinda of machine we on?
				 * hopfully the C compiler will
				 * optimize this out...
				 *
				 * For shorts, we want sign extend
				 * for %d but not for %[oxu] -
				 * on 16 bit machines it don't
				 * matter.
				 * Assmumes C compiler has converted
				 * shorts to ints before pushing them.
				 */
				if (sizeof(int) < sizeof(long))
					lnum = (c == 'd') ? (long)
						va_arg(args, int) :
						va_arg(args, unsigned);
				    else
					lnum = va_arg(args, unsigned);
				break;

			case SZ_LONG:
				lnum = va_arg(args, unsigned long);
				}

		else if (c == 'e' || c == 'g' || c == 'f')
			{
			dnum = va_arg(args, double);
			}

		switch (c) 
			{
		case 'd':
			if (0 > (long) lnum)
				lnum = - (long) lnum, neg = 1;
			else
				neg = 0;
			s = &numbuf[sizeof(numbuf)];
			*--s = '\0';
			do 	{
				*--s = lnum % 10 + '0';
				lnum /= 10;
				} while (lnum);

			if (neg)
				*--s = '-';
			else if (signfmt > 0)
				*--s = '+';
			else if (signfmt < 0)
				*--s = ' ';
			goto put_string;

		case 'o':
			s = &numbuf[sizeof(numbuf)];
			*--s = '\0';
			do 	{
				*--s = (lnum & 0x7) + '0';
				lnum >>= 3;
				} while (lnum);

			if (hash && s[1])
				*--s = '0';
			goto put_string;

		case 'x':
			s = &numbuf[sizeof(numbuf)];
			*--s = '\0';
			p = upper ? "0123456789ABCDEF" :
				"0123456789abcdef";
			do 	{
				*--s = p[lnum & 0x0F];
				lnum >>= 4;
				} while (lnum);

			if (hash && s[1])
				*--s = upper ? 'X' : 'x', *--s = '0';
			goto put_string;

		case 'u':
			s = &numbuf[sizeof(numbuf)];
			*--s = '\0';
			do 	{
				*--s = lnum % 10 + '0';
				lnum /= 10;
				} while (lnum);
			goto put_string;

		case 'c':
			(* fun)(va_arg(args, int), funargs);
			break;

		case '%':
			(* fun)('%', funargs);
			break;

		case 'e': case 'g': case 'f':
			{
			{
			char dbuf[64];

			if (precision == 32000)
				precision = -1;
			s = _flofmt(&dbuf, 0, dnum, precision, field);
			if (precision < strlen(s))
				precision = strlen(s);
					/* don't confuse string copier. */
			};
			goto put_string;
			};

		case 's':
			s = va_arg(args, char *);
			/* fall through */

put_string:
			/* we have a string, now format & print it */
			tmp = strlen(s);
			if (tmp < precision)
				precision = tmp;
			if (field > precision) 
				{
				tmp = field - precision;
				if (adjust)
					adjust = tmp, pad = ' ';
				else
					adjust = -tmp;
				}
			    else
			if (field == precision)
				adjust = 0;
			while (adjust < 0) 
				{
				if ((*s == '-') && (pad == '0')) 
					{
					(* fun)(*s++, funargs);
					precision--;
					}
				(* fun)(pad, funargs);
				adjust++;
				}
			while (--precision >= 0)
				(* fun)(*s++, funargs);
			while (adjust) 
				{
				(* fun)(pad, funargs);
				adjust--;
				}
			}
		} 
	    else
		(* fun)(c, funargs);
	}
}
