/*							  _ v f p r i n t f 						   */

/* This function performs all the formatted printing. Floating point
 * requests are forwarded to lower level floating point conversion
 * functions. The function must not drag in all of the stdio library
 * so that sprintf() can be used by programs which don't necessarily
 * want the rest of stdio.
 */

#include "stdiolib.h"
#include <ctype.h>

/*LINTLIBRARY*/

/* Number to string conversion
 *
 * Separate routines are provided to convert numbers from binary to
 * string representations for each base. Furthermore, long to int
 * front end functions are provided in order to improve performance
 * of long conversions.
 */
 
/* These definitions provide the masks necessary to convert longs
 * where longs are wider than the natural word size. The %ld format
 * requires the largest power of ten that will fit into an integer.
 * For an integer of N bits, highest power of ten that will fit in
 * N.log_10 2.
 */

#if LONG_MAX != INT_MAX
# define UINTBITS	(CHAR_BIT * sizeof(unsigned int))
# define HEXSHIFT	(UINTBITS/4*4)
# define HEXMASK	((unsigned int) ((1L << HEXSHIFT) - 1))
# define HEXDIGITS	(UINTBITS/4)
# define OCTSHIFT	(UINTBITS/3*3)
# define OCTMASK	((unsigned int) ((1L << OCTSHIFT) - 1))
# define OCTDIGITS	(UINTBITS/3)
# define DECDIGITS	(UINTBITS * 301 / 1000)
#endif

/* Radix descriptor table entry */

typedef struct radix {
  char *(*utoa) P((unsigned int, char *, char *));
					/* int to string function */
#if LONG_MAX != INT_MAX
  char *(*ultoa) P((unsigned long, char *, struct radix *));
					/* long to string function */
  int lshift;				/* bit shift */
  unsigned int lmask;			/* bit mask */
  int ldigits;				/* digits per section */
#endif
  char *conv;				/* conversion table */
} RADIX;

/* Function prototypes for unsigned and unsigned long convertors */

typedef char * (*utof) P((unsigned int, char *, char *));
typedef char * (*ultof) P((unsigned int, char *, RADIX *));

/* unsigned int to string convertors */

static char *__utod F3(register unsigned int, n,
					   register char *, p,
					   char *, conv)

{
  unsigned int q;

  do {
	q	 = n / 10;
	*--p = (n - q*10) + '0';
  } while ((n = q) != 0);
  return p;
}

static char *__utoo F3(register unsigned int, n,
					   register char *, p,
					   char *, conv)

{
  do {
	*--p = (n & 0x7) + '0';
  } while ((n >>= 3) != 0);
  return p;
}

static char *__utox F3(register unsigned int, n,
					   register char *, p,
					   char *, conv)

{
  do {
	*--p = conv[n & 0xf];
  } while ((n >>= 4) != 0);
  return p;
}

/* unsigned long to string convertors */

#if LONG_MAX != INT_MAX
static char *__ultod F3(unsigned long, n, char *, p, RADIX *, r)

{
  unsigned long q;
  unsigned int d;
  char *t;
  int i;
  
  ASSERT(DECDIGITS <= __Mipow10);

  for (;;) {
	q = n / __ipow10[DECDIGITS];
	d = n - q * __ipow10[DECDIGITS];
	t = (*r->utoa)(d, p, NULL);
	if ((n = q) == 0)
	  break;
	for (i = DECDIGITS - (p - t); i--; )
	  *--t = '0';
	p = t;
  }
  return t;
}

static char *__ultob F3(unsigned long, n, char *, p, RADIX *, r)

{
  int i;
  char *t;

  for (;;) {
	t = (*r->utoa)((unsigned int) n & r->lmask, p, r->conv);
	if ((n >>= r->lshift) == 0)
	  break;
	for (i = r->ldigits - (p - t); i--; )
	  *--t = '0';
	p = t;
  }
  return t;
}
#endif

/* Digit to character conversion table */

static char __lconv[] = "0123456789abcdef";
static char __uconv[] = "0123456789ABCDEF";

/* Entries in radix descriptor table */

#define N_RADIX 	(sizeof(__radix)/sizeof(__radix[0]))
#define R_DECIMAL	0
#define R_HEXL		1
#define R_HEXU		2
#define R_OCTAL 	3

/* Radix descriptor table */

#if LONG_MAX != INT_MAX
#define LONG(ultoa,lshift,lmask,ldigits) (ultoa),(lshift),(lmask),(ldigits),
#else
#define LONG(ultoa,lshift,lmask,ldigits)
#endif

static RADIX __radix[] = {
  {__utod, LONG(__ultod, 0, 0, 0)        NULL},         /* %d %u */
  {__utox, LONG(__ultob, HEXSHIFT, HEXMASK, HEXDIGITS) __lconv},/* %x */
  {__utox, LONG(__ultob, HEXSHIFT, HEXMASK, HEXDIGITS) __uconv},/* %X */
  {__utoo, LONG(__ultob, OCTSHIFT, OCTMASK, OCTDIGITS) NULL},   /* %o */
};

/* Formatted component vector print
 *
 * This function prints component vectors of the output string.
 */
 
static void __printv F3(register FILE *, fp, int, vc, register FV *, vp)

{
  __stdiobuf_t *wp; 		/* output buffer pointer */
  __stdiobuf_t *p;			/* buffer pointer */
  int pad;				/* padding only */
  __stdiosize_t wb; 		/* bytes to write */
  __stdiosize_t wf; 		/* free bytes in buffer */
  __stdiosize_t len;			/* length of string */

  for (len = 0; ;) {

/* Gauge the amount of free space in the stream buffer */
	if (! TESTFLAG(fp, _IONBF))
	  wf = UNUSEDINWRITEBUFFER(fp);
	else {
	  fp->__wptr = fp->__wend = fp->__base = &fp->__buf;
	  wf = 0;
	}
	wp = GETWRITEPTR(fp);

	for (;;) {
	  if ((wb = len) != 0 && wf != 0) {

	if (wb > wf)
	  wb = wf;

	wf	-= wb;
	len -= wb;

#ifndef GENERIC
	/*
	 *	We can avoid the function call overhead for the special
	 *	case of a single character (which seems to be quite frequent)
	 */
	if (wb == 1)
	{
		*wp = *p;
		if (pad == 0)
			p += wb;
	}
	else
#endif /* ! GENERIC */

	if (pad != 0)
	  MEMSET((char *) wp, *p, (size_t) wb);
	else {
	  MEMCPY((char *) wp, (char *) p, (size_t) wb);
	  p += wb;
	}

	wp += wb;

	  }

/* Determine the nature and size of the next string to write */
	  if (len == 0) {
	if (vc == 0) {
	  SETWRITEPTR(fp, wp);
	  return;
	}

	pad = vp->att & FV_F_PADDING;
	len = vp->len;
	p	= (__stdiobuf_t *) vp->arg;
	vc--;
	vp++;
	continue;
	  }

/* Flush a filled stream buffer */
	  if (TESTFLAG(fp, _IONBF)) {
	fp->__base = p;
	if (pad != 0 && wb > PWRITEAHEAD)
	  wb = PWRITEAHEAD;
	len -= wb;
	wp	 = p + wb;
	  }
	  SETWRITEPTR(fp, wp);
	  (void) FFLUSH(fp);
	  break;
	}
  }
}

/* Filler strings */

#if PWRITEAHEAD != 4
  << Print buffer write ahead assumed to be 4 >>
#else
char __zfill[PWRITEAHEAD] = {           /* zero fill */
  '0', '0', '0', '0' };
static char __bfill[PWRITEAHEAD] = {        /* blank fill */
  ' ', ' ', ' ', ' ' };
#endif

/* Format indicator */

#if 0 < '%'
# define FMTCH(x)	( (x) <= '%' )
#else
# define FMTCH(x)	( (x) >= '%' )
#endif

int __vfprintf F3(FILE *, fp, CONST char *, fmt, VA_LIST, args)

{
  register __stdiobuf_t *wp;		/* direct write pointer */
  register __stdiobuf_t *sp;		/* format string pointer */
  register __stdiobuf_t *we;		/* end of output buffer */
  __stdiobuf_t *pb; 		/* line buffered scan point */
  __stdiobuf_t *sb; 		/* base of this format */

  int c;				/* conversion temporary */

  char *p, *q;				/* pointers into workspace */

  int flag; 			/* flags */

  FV leftfill;				/* left fill */
  
  __stdiosize_t bytes;			/* bytes output */
  __stdiosize_t width;			/* field width */
  __stdiosize_t precision;		/* precision */
  __stdiosize_t length; 		/* raw length of output */

  char sign;				/* conversion is signed */
  char negative;			/* number is negative */
  long vl;				/* conversion temporary */
  RADIX *radix; 			/* radix descriptor */
  int vi;				/* conversion temporary */

  int fvc;				/* vector count */
  int fvx;				/* vector index */
  FV *fvp;				/* vector scanner */
  FV fv[FV_ARGS];			/* formatted vectors */

  char buf[PBUFFERSIZE];		/* workspace */

  if (CHECKWRITE(fp))
	return 0;

/* Initialise once for buffered streams */
  fv[FV_FMTPREFIX].att = FV_F_VECTOR;
  fv[FV_FMTPREFIX].len = 0;

/* Buffer start for line buffered streams */
  pb = GETWRITEPTR(fp);
  
  bytes = 0;

  for (sp = (__stdiobuf_t *) fmt; ;) {

/* Scan for format specifier */
	sb = sp;
	if (TESTFLAG(fp, _IONBF)) {
	  fv[FV_FMTPREFIX].arg = (char *) sb;
	  if ((sp = strchr((char *) sb, '%')) != NULL)
	fv[FV_FMTPREFIX].len = sp - sb;
	  else {
	length = strlen((char *) sb);
	bytes += length;
	sb = sp = "";
	fv[FV_FMTPREFIX].len = length;
	__printv(fp, 1, &fv[0]);
	  }
	}
	else {
	  for (;;) {

	wp = GETWRITEPTR(fp);
	we = GETWRITELIMIT(fp);

	if (BUFFERSIZE(fp) >= PWRITEAHEAD) {
	  we -= PWRITEAHEAD;
	  while (wp < we) {
		if (FMTCH(wp[0] = sp[0]) && (wp[0] == 0 || wp[0] == '%'))
		  goto Format0;
		if (FMTCH(wp[1] = sp[1]) && (wp[1] == 0 || wp[1] == '%'))
		  goto Format1;
		if (FMTCH(wp[2] = sp[2]) && (wp[2] == 0 || wp[2] == '%'))
		  goto Format2;
		if (FMTCH(wp[3] = sp[3]) && (wp[3] == 0 || wp[3] == '%'))
		  goto Format3;
		sp += PWRITEAHEAD;
		wp += PWRITEAHEAD;
	  }
	  we += PWRITEAHEAD;
	}
	for (; wp < we; wp++, sp++) {
	  if (FMTCH(wp[0] = sp[0]) && (wp[0] == 0 || wp[0] == '%'))
		goto Format0;
	}
	SETWRITEPTR(fp, wp);
	FFLUSH(fp);
	  }

Format3: wp += 3; sp += 3; goto Format0;
Format2: wp += 2; sp += 2; goto Format0;
Format1: wp += 1; sp += 1; goto Format0;
Format0:

	  SETWRITEPTR(fp, wp);
	}

/* Format or end of format found */
	bytes += sp - sb;

	if (*sp == 0)
	  break;
	sp++;

/* Initialise conversion variables */
	p = q = &buf[sizeof(buf)-1];

	flag		 = 0;
	leftfill.arg = __bfill;
	width		 = 0;

/* Switch through all the format options */
Parse_again:
	switch (c = *sp++) {

	  case '-': flag |= F_LEFTJUSTIFY; goto Parse_again;
	  case '+': flag |= F_SHOWSIGN;    goto Parse_again;
	  case ' ': flag |= F_BLANKPREFIX; goto Parse_again;
	  case '#': flag |= F_ALTERNATE;   goto Parse_again;

/* Check for field width and precision */
	  case '*':
	if ((c = VA_ARG(args, int)) < 0) {
	  flag |= F_LEFTJUSTIFY;
	  c = -c;
	}
	width = c;
	c = *sp++;
	goto Precision;

/* Zero fill and field width */
	  case '0':
	leftfill.arg = __zfill;
	c = *sp++;

	  case '1': case '2': case '3': case '4': case '5':
	  case '6': case '7': case '8': case '9':
	for (; c >= '0' && c <= '9'; c = *sp++) {
	  width *= 10;
	  width += (c - '0');
	}

	  case '.':
Precision:
	if (c == '.') {
	  c = *sp++;
	  if (c == '*') {
		if ((c = VA_ARG(args, int)) >= 0) {
		  flag |= F_PRECISION;
		  precision = c;
		}
		sp++;
	  }
	  else if (c >= '0' && c <= '9') {
		flag |= F_PRECISION;
		precision = 0;
		for (; c >= '0' && c <= '9'; c = *sp++) {
		  precision *= 10;
		  precision += (c - '0');
		}
	  }
	}
	sp--;
	goto Parse_again;

/* Short indication */
	  case 'h':
	flag = (flag & ~(F_SHORT | F_LONG | F_LONGDOUBLE)) | F_SHORT;
	goto Parse_again;

/* Long indication */
	  case 'l':
	flag = (flag & ~(F_SHORT | F_LONG | F_LONGDOUBLE)) | F_LONG;
	goto Parse_again;

/* Long double indication */
	  case 'L':
	flag = (flag & ~(F_SHORT | F_LONG | F_LONGDOUBLE)) | F_LONGDOUBLE;
	goto Parse_again;

/* Number of bytes output */
	  case 'n':
	*(VA_ARG(args, int *)) = bytes;
	continue;

/* Pointer */
	  case 'p':
	flag = (flag & ~(F_SHORT | F_LONG | F_LONGDOUBLE)) |
		   F_LONG * (sizeof(void *) == sizeof(long));
	sign  = 0;
	radix = &__radix[R_HEXL];
	goto oxud;

/* Integer conversions */
	  case 'X':
	flag |= F_0X;
	sign  = 0;
	radix = &__radix[R_HEXU];
	goto oxud;

	  case 'x':
	sign  = 0;
	radix = &__radix[R_HEXL];
	goto oxud;

	  case 'u':
	sign  = 0;
	radix = &__radix[R_DECIMAL];
	goto oxud;

	  case 'o':
	sign  = 0;
	radix = &__radix[R_OCTAL];
	goto oxud;

	  case 'i':
	  case 'd':
		sign  = 1;
	radix = &__radix[R_DECIMAL];

oxud:
	if ((flag & F_LONG) != 0) {
	  if (sign) vl = VA_ARG(args, long);
	  else		vl = VA_ARG(args, unsigned long);
	  if ((negative = sign && vl < 0) != 0)
		vl = -vl;
#if LONG_MAX != INT_MAX
	  q = (*radix->ultoa)((unsigned long) vl, q, radix);
#else
	  q = (*radix->utoa)((unsigned int) vl, q, radix->conv);
#endif
	}
	else {
	  if ((flag & F_SHORT) != 0)
		if (sign) vi = (int) VA_ARG(args, short);
		else	  vi = (int) VA_ARG(args, unsigned short);
	  else
		if (sign) vi = (int) VA_ARG(args, int);
		else	  vi = (int) VA_ARG(args, unsigned);
	  if ((negative = (sign && vi < 0)) != 0)
		vi = -vi;
	  q = (*radix->utoa)((unsigned int) vi, q, radix->conv);
	}

	if ((flag & F_PRECISION) != 0 && precision > width) {
	  width 	   = precision;
	  leftfill.arg = __zfill;
	}

		fv[FV_INTINT].att  = FV_F_VECTOR;
		fv[FV_INTINT].len  = p - q;
		fv[FV_INTINT].arg  = q;
		fv[FV_INTSIGN].att = FV_F_VECTOR;
		fv[FV_INTSIGN].len = 0;
		fvc = FV_INTARGS;

	if (negative) {
	  fv[FV_INTSIGN].len++;
	  *--q = '-';
	}
	else if (sign) {
	  if ((flag & F_SHOWSIGN) != 0) {
		fv[FV_INTSIGN].len++;
		*--q = '+';
	  }
	  else if ((flag & F_BLANKPREFIX) != 0) {
		fv[FV_INTSIGN].len++;
		*--q = ' ';
	  }
	}
	if ((flag & F_ALTERNATE) != 0) {
	  if (radix == &__radix[R_OCTAL]) {
		fv[FV_INTSIGN].len++;
		*--q = '0';
	  }
	  else if (radix == &__radix[R_HEXL] || radix == &__radix[R_HEXU]) {
		fv[FV_INTSIGN].len += 2;
		*--q = (flag & F_0X) != 0 ? 'X' : 'x';
			*--q = '0';
	  }
	}
		fv[FV_INTSIGN].arg = q;
		length			   = p - q;
	break;

/* Floating point formats */
	  case 'f':
	flag |= F_FPFORMATF;
	goto Fconvert;

	  case 'E':
	flag |= F_FPCAPITAL;
	  case 'e':
	flag |= F_FPFORMATE;
	goto Fconvert;

	  case 'G':
	flag |= F_FPCAPITAL;
	  case 'g':
	flag |= F_FPFORMATG;
Fconvert:
	if ((flag & F_PRECISION) == 0) precision = 6;
	fvc = FV_FMTARGS
		+ __cvt(&length, &fv[FV_FMTARGS], buf, &args, precision, flag);
	ASSERT(fvc <= FV_ARGS);
	break;

/* Single character format */
	  case 'c':
	*--q = VA_ARG(args, int);
	goto Simple_vector;

/* String format */
	  case 's':
	if ((q = VA_ARG(args, char *)) == NULL)
	  q = "(null)";
	if ((flag & F_PRECISION) == 0)
	  p = q + strlen(q);
	else {
	  if ((p = (char *) MEMCHR(q, 0, (size_t) precision)) == NULL)
		p = q + precision;
	}
	goto Simple_vector;

/* Default just print it */
	  default:
	*--q = sp[-1];

/* Construct a simple vector */
Simple_vector:
	length = p - q;
	fv[FV_INTINT].att  = FV_F_VECTOR;
	fv[FV_INTINT].len  = length;
	fv[FV_INTINT].arg  = q;
	fv[FV_INTSIGN].att = FV_F_VECTOR;
	fv[FV_INTSIGN].len = 0;
		fvc = FV_INTARGS;
	break;
	}

/* Start vector index */
	fvx    = 0;
	fvp    = &fv[0];
	bytes += length;

/* Subtract to find padding required --- no padding */
	if (width <= length) {
	  width = 0;

	  fv[FV_FMTARGS+1].len += fv[FV_FMTARGS].len;
	  fv[FV_FMTARGS+1].arg -= fv[FV_FMTARGS].len;
	  fv[FV_FMTARGS].len	= 0;
	}

/* Some padding required (may be right or left) */
	else {
	  width -= length;

/* Required right justification (padding on left) */
	  if ((flag & F_LEFTJUSTIFY) == 0) {

/* Print format string first */
#if FV_FMTARGS != 1
	<< FV_FMTARGS assumed to be 1 >>
#else
	if (fvp->len > 0)
	  __printv(fp, FV_FMTARGS, fvp);
#endif
	fvp += FV_FMTARGS;
	fvx += FV_FMTARGS;

/* Check for negative and zero fill */
	if (leftfill.arg == __zfill && fv[FV_FMTARGS].len != 0) {
	  __printv(fp, 1, &fv[FV_FMTARGS]);
	  fvx++;
	  fvp++;
	}

/* Now output the rest of the padding */
	bytes		+= width;
	leftfill.att = FV_F_PADDING;
	leftfill.len = width;
	width		 = 0;
	__printv(fp, 1, &leftfill);
	  }
	}

/* Output the string proper */
	__printv(fp, fvc-fvx, fvp);

/* Check for required right padding */
	if (width != 0) {
	  bytes 	  += width;
	  leftfill.att = FV_F_PADDING;
	  leftfill.len = width;
	  leftfill.arg = __bfill;
	  __printv(fp, 1, &leftfill);
	}
  }

/* Flush line buffered streams */
  if (TESTFLAG(fp, _IOLBF)) {
	length = bytes;
	if (bytes + pb != GETWRITEPTR(fp)) {
	  pb   = fp->__base;
	  length = BYTESINWRITEBUFFER(fp);
	}
	pb = (__stdiobuf_t *) MEMCHR(pb, '\n', (size_t) length);
	if (pb != NULL)
	  (void) FFLUSH(fp);
  }

  return ferror(fp) ? EOF : bytes;
}
