xprintf.c 5.69 KB
/* _Printf function */
#include <stdlib.h>
#include "xstdio.h"
#include <string.h>

/* macros */

#if _DLONG
#define LDSIGN(x) (((unsigned short *) &(x))[_D0 ? 4 : 0] & 0x8000)
#else
#define LDSIGN(x) (((unsigned short *) &(x))[_D0] & 0x8000)
#endif

#define ISDIGIT(c)	((c >= '0') && (c <= '9'))

#define MAX_PAD (sizeof(spaces) - 1)

#define PAD(s, n) if (0 < (n))                           \
                  {                                      \
                      int i, j = (n);                    \
                      for (; 0 < j; j -= i)              \
                      {                                  \
                          i = MAX_PAD < j ? MAX_PAD : j; \
                          PUT(s, i);                     \
                      }                                  \
                  }

#define PUT(s, n) if (0 < (n))                               \
                  {                                          \
                      if ((arg = (*pfn)(arg, s, n)) != NULL) \
                          x.nchar += (n);                    \
                      else                                   \
                          return (x.nchar);                  \
                  }

static char spaces[] = "                                ";
static char zeroes[] = "00000000000000000000000000000000";

static void _Putfld(_Pft *, va_list *, char, char *);

int _Printf(void *(*pfn)(void *, const char *, size_t),
	void *arg, const char *fmt, va_list ap)
{	/* print formatted */
    _Pft x;

    x.nchar = 0;
    while (1) 	/* scan format string */
    {
	const char *s = fmt;
	char c;
	const char *t;
	static const char fchar[] = {" +-#0"};
	static const unsigned int fbit[] = {_FSP, _FPL, _FMI, _FNO, _FZE, 0};
	char ac[32];

	/* copy any literal text */
	for (c = *s; (c != 0) && (c != '%'); )
	  c = *++s;
	
	PUT(fmt, s - fmt);
	if (c == '\0')
	    return (x.nchar);
	fmt = ++s;
	
	/* parse a conversion specifier */
	for (x.flags = 0; (t = strchr(fchar, *s)) != NULL; ++s)
	    x.flags |= fbit[t - fchar];
	if (*s == '*') 	/* get width argument */
	{
	    x.width = va_arg(ap, int);
	    if (x.width < 0) 	/* same as '-' flag */
	    {
		x.width = -x.width;
		x.flags |= _FMI;
	    }
	    ++s;
	} 
	else	/* accumulate width digits */
	    for (x.width = 0; ISDIGIT(*s); ++s)
		if (x.width < _WMAX)
		    x.width = x.width * 10 + *s - '0';

	if (*s != '.')
	    x.prec = -1;
	else if (*++s == '*') 	/* get precision argument */
	{
	    x.prec = va_arg(ap, int);
	    ++s;
	} 
	else	/* accumulate precision digits */
	    for (x.prec = 0; ISDIGIT(*s); ++s)
		if (x.prec < _WMAX)
		    x.prec = x.prec * 10 + *s - '0';

	x.qual = strchr("hlL", *s) ? *s++ : '\0';
	if ((x.qual == 'l') && (*s == 'l')) 
	{
	    x.qual = 'L';	/* the %ll qualifier */
	    s++;
	}
	
	/* do the conversion */
	_Putfld(&x, &ap, *s, ac);
	x.width -= x.n0 + x.nz0 + x.n1 + x.nz1 + x.n2 + x.nz2;
	if (!(x.flags & _FMI))
	    PAD(spaces, x.width);
	PUT(ac, x.n0);
	PAD(zeroes, x.nz0);
	PUT(x.s, x.n1);
	PAD(zeroes, x.nz1);
	PUT(x.s + x.n1, x.n2);
	PAD(zeroes, x.nz2);
	if (x.flags & _FMI)
	    PAD(spaces, x.width);
	fmt = s + 1;
    }
    /* shouldn't reach here, only used to eliminate the compiler warning */
    return 0;
}

static void _Putfld(_Pft *px, va_list *pap, char code, char *ac)
{	/* convert a field for _Printf */
    
    px->n0 = px->nz0 = px->n1 = px->nz1 = px->n2 = px->nz2 = 0;
    switch (code) 	/* switch on conversion specifier */
    {
	case 'c':
	    ac[px->n0++] = va_arg(*pap, int);
	    break;
	case 'd':
	case 'i':	/* convert a signed decimal integer */
	    if (px->qual == 'l')
		px->v.ll = va_arg(*pap, long);
	    else if (px->qual == 'L')
		px->v.ll = va_arg(*pap, long long);
	    else
		px->v.ll = va_arg(*pap, int);
	    if (px->qual == 'h')
		px->v.ll = (short) px->v.ll;
	    if (px->v.ll < 0)	/* negate safely in _Litob */
		ac[px->n0++] = '-';
	    else if (px->flags & _FPL)
		ac[px->n0++] = '+';
	    else if (px->flags & _FSP)
		ac[px->n0++] = ' ';
	    px->s = &ac[px->n0];
	    _Litob(px, code);
	    break;
	case 'o':
	case 'u':
	case 'x':
	case 'X':	/* convert unsigned */
	    if (px->qual == 'l')
		px->v.ll = va_arg(*pap, long);
	    else if (px->qual == 'L')
		px->v.ll = va_arg(*pap, long long);
	    else
		px->v.ll = va_arg(*pap, int);
	    if (px->qual == 'h')
		px->v.ll = (unsigned short) px->v.ll;
	    else if (px->qual == '\0')
		px->v.ll = (unsigned int) px->v.ll;
	    if (px->flags & _FNO) {	/* indicate base with prefix */
		ac[px->n0++] = '0';
		if (code == 'x' || code == 'X')
		    ac[px->n0++] = code;
	    }
	    px->s = &ac[px->n0];
	    _Litob(px, code);
	    break;
	case 'e':
	case 'E':
	case 'f':
	case 'g':
	case 'G':	/* convert floating */
	    px->v.ld = px->qual == 'L' ?
		va_arg(*pap, ldouble) : va_arg(*pap, double);
	    if (LDSIGN(px->v.ld))
		ac[px->n0++] = '-';
	    else if (px->flags & _FPL)
		ac[px->n0++] = '+';
	    else if (px->flags & _FSP)
		ac[px->n0++] = ' ';
	    px->s = &ac[px->n0];
	    _Ldtob(px, code);
	    break;
	case 'n':	/* return output count */
	    if (px->qual == 'h')
		*va_arg(*pap, short *) = px->nchar;
	    else if (px->qual == 'l')
		*va_arg(*pap, long *) = px->nchar;
	    else if (px->qual == 'L')
		*va_arg(*pap, long long *) = px->nchar;
	    else
		*va_arg(*pap, int *) = px->nchar;
	    break;
	case 'p':	/* convert a pointer, hex long version */
	    px->v.ll = (long) va_arg(*pap, void *);
	    px->s = &ac[px->n0];
	    _Litob(px, 'x');
	    break;
	case 's':	/* convert a string */
	    px->s = va_arg(*pap, char *);
	    px->n1 = strlen(px->s);
	    if (0 <= px->prec && px->prec < px->n1)
		px->n1 = px->prec;
	    break;
	case '%':	/* put a '%' */
	    ac[px->n0++] = '%';
	    break;
	default:	/* undefined specifier, print it out */
	    ac[px->n0++] = code;
	    break;
    }
}