/* @(#)format.c	1.40 06/09/13 Copyright 1985-2003 J. Schilling */
/*
 *	format
 *	common code for printf fprintf & sprintf
 *
 *	allows recursive printf with "%r", used in:
 *	error, comerr, comerrno, errmsg, errmsgno and the like
 *
 *	Copyright (c) 1985-2003 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#include <schily/mconfig.h>
#include <schily/varargs.h>
#include <schily/string.h>
#include <schily/stdlib.h>
#ifdef	DEBUG
#include <schily/unistd.h>
#endif
#if	!defined(HAVE_STDLIB_H) || !defined(HAVE_GCVT)
extern	char	*gcvt __PR((double, int, char *));
#endif
#include <schily/standard.h>
#include <schily/utypes.h>
#include <schily/schily.h>

/*
 * As Llong is currently a 'best effort' long long, we usually need to
 * include long long print formats.
 * This may go away, if we implement maxint_t formats.
 */
#define	USE_LONGLONG

#ifdef	NO_LONGLONG
#undef	USE_LONGLONG
#endif

/*
 * Some CPU's (e.g. PDP-11) cannot do logical shifts.
 * They use rotate instead. Masking the low bits before,
 * makes rotate work too.
 */
#define	allmask(t)	((unsigned t)~((unsigned t)0))
#define	lowmask(t, x)	((unsigned t)~((unsigned t)((1 << (x))-1)))
#define	rshiftmask(t, s)((allmask(t) & lowmask(t, s)) >> (s))

#define	CHARMASK	makemask(char)
#define	SHORTMASK	makemask(short)
#define	INTMASK		makemask(int)
#define	LONGMASK	makemask(long)

#ifdef	DIVLBYS
extern	long	divlbys();
extern	long	modlbys();
#else
#define	divlbys(val, base)	((val)/(base))
#define	modlbys(val, base)	((val)%(base))
#endif

/*
 *	We use macros here to avoid the need to link to the international
 *	character routines.
 *	We don't need internationalization for our purpose.
 */
#define	is_dig(c)	(((c) >= '0') && ((c) <= '9'))
#define	is_cap(c)	((c) >= 'A' && (c) <= 'Z')
#define	to_cap(c)	(is_cap(c) ? c : c - 'a' + 'A')
#define	cap_ty(c)	(is_cap(c) ? 'L' : 'I')

#ifdef	HAVE_LONGLONG
typedef union {
	Ullong	ll;
	Ulong	l[2];
	char	c[8];
} quad_u;
#endif

typedef struct f_args {
	void  (*outf)__PR((char, long)); /* Func from format(fun, arg)	*/
	long	farg;			/* Arg from format (fun, arg)	*/
	int	minusflag;		/* Fieldwidth is negative	*/
	int	flags;			/* General flags (+-#)		*/
	int	fldwidth;		/* Field width as in %3d	*/
	int	signific;		/* Significant chars as in %.4d	*/
	int	lzero;			/* Left '0' pad flag		*/
	char	*buf;			/* Out print buffer		*/
	char	*bufp;			/* Write ptr into buffer	*/
	char	fillc;			/* Left fill char (' ' or '0')	*/
	char	*prefix;		/* Prefix to print before buf	*/
	int	prefixlen;		/* Len of prefix ('+','-','0x')	*/
} f_args;

#define	MINUSFLG	1	/* '-' flag */
#define	PLUSFLG		2	/* '+' flag */
#define	SPACEFLG	4	/* ' ' flag */
#define	HASHFLG		8	/* '#' flag */

LOCAL	void	prnum  __PR((Ulong, unsigned, f_args *));
LOCAL	void	prdnum __PR((Ulong, f_args *));
LOCAL	void	pronum __PR((Ulong, f_args *));
LOCAL	void	prxnum __PR((Ulong, f_args *));
LOCAL	void	prXnum __PR((Ulong, f_args *));
#ifdef	USE_LONGLONG
LOCAL	void	prlnum  __PR((Ullong, unsigned, f_args *));
LOCAL	void	prldnum __PR((Ullong, f_args *));
LOCAL	void	prlonum __PR((Ullong, f_args *));
LOCAL	void	prlxnum __PR((Ullong, f_args *));
LOCAL	void	prlXnum __PR((Ullong, f_args *));
#endif
LOCAL	int	prbuf  __PR((const char *, f_args *));
LOCAL	int	prc    __PR((char, f_args *));
LOCAL	int	prstring __PR((const char *, f_args *));
#ifdef	DEBUG
LOCAL	void	dbg_print __PR((char *fmt, int a, int b, int c, int d, int e, int f, int g, int h, int i));
#endif


#ifdef	PROTOTYPES
EXPORT int
format(void (*fun)(char, long),
			long farg,
			const char *fmt,
			va_list args)
#else
EXPORT int
format(fun, farg, fmt, args)
	register void	(*fun)();
	register long	farg;
	register char	*fmt;
	va_list		args;
#endif
{
#ifdef	FORMAT_LOW_MEM
	char buf[512];
#else
	char buf[8192];
#endif
	const char *sfmt;
	register int unsflag;
	register long val;
	register char type;
	register char mode;
	register char c;
	int count;
	int i;
	short sh;
	const char *str;
	double dval;
#ifdef	USE_LONGLONG
	Llong llval = 0;
#endif
	Ulong res;
	char *rfmt;
	f_args	fa;

	fa.outf = fun;
	fa.farg = farg;
	count = 0;
	/*
	 * Main loop over the format string.
	 * Increment and check for end of string is made here.
	 */
	for (; *fmt != '\0'; fmt++) {
		c = *fmt;
		while (c != '%') {
			if (c == '\0')
				return (count);
			(*fun)(c, farg);
			c = *(++fmt);
			count++;
		}

		/*
		 * We reached a '%' sign.
		 */
		buf[0] = '\0';
		fa.buf = fa.bufp = buf;
		fa.minusflag = 0;
		fa.flags = 0;
		fa.fldwidth = 0;
		fa.signific = -1;
		fa.lzero = 0;
		fa.fillc = ' ';
		fa.prefixlen = 0;
		sfmt = fmt;
		unsflag = FALSE;
		type = '\0';
		mode = '\0';
	newflag:
		switch (*(++fmt)) {

		case '+':
			fa.flags |= PLUSFLG;
			goto newflag;

		case '-':
			fa.minusflag++;
			goto newflag;

		case ' ':
			/*
			 * If the space and the + flag are present,
			 * the space flag will be ignored.
			 */
			fa.flags |= SPACEFLG;
			goto newflag;

		case '#':
			fa.flags |= HASHFLG;
			goto newflag;

		case '0':
			/*
			 * '0' is a flag.
			 */
			fa.fillc = '0';
			goto newflag;
		}
		if (*fmt == '*') {
			fmt++;
			fa.fldwidth = va_arg(args, int);
			/*
			 * A negative fieldwith is a minus flag with a
			 * positive fieldwidth.
			 */
			if (fa.fldwidth < 0) {
				fa.fldwidth = -fa.fldwidth;
/*				fa.minusflag ^= 1;*/
				fa.minusflag = 1;
			}
		} else while (c = *fmt, is_dig(c)) {
			fa.fldwidth *= 10;
			fa.fldwidth += c - '0';
			fmt++;
		}
		if (*fmt == '.') {
			fmt++;
			fa.signific = 0;
			if (*fmt == '*') {
				fmt++;
				fa.signific = va_arg(args, int);
				if (fa.signific < 0)
					fa.signific = 0;
			} else while (c = *fmt, is_dig(c)) {
				fa.signific *= 10;
				fa.signific += c - '0';
				fmt++;
			}
		}
		if (strchr("UCSIL", *fmt)) {
			/*
			 * Enhancements to K&R and ANSI:
			 *
			 * got a type specifyer
			 *
			 * XXX 'S' in C99 is %ls, 'S' should become 'H'
			 */
			if (*fmt == 'U') {
				fmt++;
				unsflag = TRUE;
			}
			if (!strchr("CSILZODX", *fmt)) {
				/*
				 * Got only 'U'nsigned specifyer,
				 * use default type and mode.
				 */
				type = 'I';
				mode = 'D';
				fmt--;
			} else if (!strchr("CSIL", *fmt)) {
				/*
				 * no type, use default
				 */
				type = 'I';
				mode = *fmt;
			} else {
				/*
				 * got CSIL
				 */
				type = *fmt++;
				if (!strchr("ZODX", mode = *fmt)) {
					fmt--;
					mode = 'D'; /* default mode */
				}
			}
		} else switch (*fmt) {

		case 'h':
			if (!type)
				type = 'H';	/* convert to short type */
			goto getmode;

		case 'l':
			if (!type)
				type = 'L';	/* convert to long type */
			goto getmode;

		case 'j':
			if (!type)
				type = 'J';	/* convert to intmax_t type */
		/*
		 * XXX Future length modifiers:
		 * XXX	'z' size_t
		 * XXX	't' ptrdiff_t
		 * XXX	'L' with double: long double
		 */

		getmode:
			if (!strchr("udioxX", *(++fmt))) {
				if (type == 'H' && *fmt == 'h') {
					type = 'C';
					goto getmode;
				}
#ifdef	USE_LONGLONG
				if (type == 'L' && *fmt == 'l') {
					type = 'Q';
					goto getmode;
				}
#endif
				fmt--;
				mode = 'D';
			} else {
				mode = *fmt;
				if (mode != 'x')
					mode = to_cap(mode);
				if (mode == 'U')
					unsflag = TRUE;
				else if (mode == 'I')	/*XXX */
					mode = 'D';
			}
			break;
		case 'x':
			mode = 'x';
			goto havemode;
		case 'X':
			mode = 'X';
			type = 'I';
			goto havemode;
		case 'u':
			unsflag = TRUE;
		/*
		 * XXX Need to remove uppercase letters for 'long'
		 * XXX in future for POSIX/C99 compliance.
		 */
			/* FALLTHRU */
		case 'o': case 'O':
		case 'd': case 'D':
		case 'i': case 'I':
		case 'z': case 'Z':
			mode = to_cap(*fmt);
		havemode:
			if (!type)
				type = cap_ty(*fmt);
#ifdef	DEBUG
/*dbg_print("*fmt: '%c' mode: '%c' type: '%c'\n", *fmt, mode, type);*/
#endif
			if (mode == 'I')	/*XXX kann entfallen*/
				mode = 'D';	/*wenn besseres uflg*/
			break;
		case 'p':
			mode = 'P';
			type = 'L';
			break;

		case '%':
			count += prc('%', &fa);
			continue;
		case ' ':
			count += prbuf("", &fa);
			continue;
		case 'c':
			c = va_arg(args, int);
			count += prc(c, &fa);
			continue;
		case 's':
			str = va_arg(args, char *);
			count += prstring(str, &fa);
			continue;
		case 'b':
			str = va_arg(args, char *);
			fa.signific = va_arg(args, int);
			count += prstring(str, &fa);
			continue;

#ifndef	NO_FLOATINGPOINT
		case 'e':
			if (fa.signific == -1)
				fa.signific = 6;
			dval = va_arg(args, double);
			ftoes(buf, dval, 0, fa.signific);
			count += prbuf(buf, &fa);
			continue;
		case 'f':
			if (fa.signific == -1)
				fa.signific = 6;
			dval = va_arg(args, double);
			ftofs(buf, dval, 0, fa.signific);
			count += prbuf(buf, &fa);
			continue;
		case 'g':
			if (fa.signific == -1)
				fa.signific = 6;
			if (fa.signific == 0)
				fa.signific = 1;
			dval = va_arg(args, double);
			gcvt(dval, fa.signific, buf);
			count += prbuf(buf, &fa);
			continue;
#else
#	ifdef	USE_FLOATINGARGS
		case 'e':
		case 'f':
		case 'g':
			dval = va_arg(args, double);
			continue;
#	endif
#endif

		case 'r':			/* recursive printf */
		case 'R':			/* recursive printf */
			rfmt  = va_arg(args, char *);
			/*
			 * I don't know any portable way to get an arbitrary
			 * C object from a var arg list so I use a
			 * system-specific routine __va_arg_list() that knows
			 * if 'va_list' is an array. You will not be able to
			 * assign the value of __va_arg_list() but it works
			 * to be used as an argument of a function.
			 * It is a requirement for recursive printf to be able
			 * to use this function argument. If your system
			 * defines va_list to be an array you need to know this
			 * via autoconf or another mechanism.
			 * It would be nice to have something like
			 * __va_arg_list() in stdarg.h
			 */
			count += format(fun, farg, rfmt, __va_arg_list(args));
			continue;

		case 'n':
			{
				int	*ip = va_arg(args, int *);

				*ip = count;
			}
			continue;

		default:			/* Unknown '%' format */
			sfmt++;			/* Dont't print '%'   */
			count += fmt - sfmt;
			while (sfmt < fmt)
				(*fun)(*(sfmt++), farg);
			if (*fmt == '\0') {
				fmt--;
				continue;
			} else {
				(*fun)(*fmt, farg);
				count++;
				continue;
			}
		}
		/*
		 * print numbers:
		 * first prepare type 'C'har, s'H'ort, 'I'nt, or 'L'ong
		 * or 'Q'ad and 'J'==maxint_t
		 */
		switch (type) {

		case 'C':
			c = va_arg(args, int);
			val = c;		/* extend sign here */
			if (unsflag || mode != 'D')
#ifdef	DO_MASK
				val &= CHARMASK;
#else
				val = (unsigned char)val;
#endif
			break;
		case 'H':
		case 'S':			/* XXX remove 'S' in future */
			sh = va_arg(args, int);
			val = sh;		/* extend sign here */
			if (unsflag || mode != 'D')
#ifdef	DO_MASK
				val &= SHORTMASK;
#else
				val = (unsigned short)val;
#endif
			break;
		case 'I':
		default:
			i = va_arg(args, int);
			val = i;		/* extend sign here */
			if (unsflag || mode != 'D')
#ifdef	DO_MASK
				val &= INTMASK;
#else
				val = (unsigned int)val;
#endif
			break;
		case 'P':
		case 'L':
			val = va_arg(args, long);
			break;
#ifdef	USE_LONGLONG
		case 'J':			/* For now Intmax_t is Llong */
		case 'Q':
			llval = va_arg(args, Llong);
			val = llval != 0;
			break;
#endif
		}

		/*
		 * Final print out, take care of mode:
		 * mode is one of: 'O'ctal, 'D'ecimal, or he'X'
		 * oder 'Z'weierdarstellung.
		 */
		fa.bufp = &buf[sizeof (buf)-1];
		*--fa.bufp = '\0';

		if (val == 0 && mode != 'D') {
		printzero:
			/*
			 * Printing '0' with fieldwidth 0 results in no chars.
			 */
			fa.lzero = -1;
			if (fa.signific >= 0)
				fa.fillc = ' ';
			count += prstring("0", &fa);
			continue;
		} else switch (mode) {

		case 'D':
#ifdef	USE_LONGLONG
			if (type == 'Q') {
				if (!unsflag && llval < 0) {
					fa.prefix = "-";
					fa.prefixlen = 1;
					llval = -llval;
				} else if (fa.flags & PLUSFLG) {
					fa.prefix = "+";
					fa.prefixlen = 1;
				} else if (fa.flags & SPACEFLG) {
					fa.prefix = " ";
					fa.prefixlen = 1;
				}
				if (llval == 0)
					goto printzero;
				goto prunsigned;
			}
#endif
			if (!unsflag && val < 0) {
				fa.prefix = "-";
				fa.prefixlen = 1;
				val = -val;
			} else if (fa.flags & PLUSFLG) {
				fa.prefix = "+";
				fa.prefixlen = 1;
			} else if (fa.flags & SPACEFLG) {
				fa.prefix = " ";
				fa.prefixlen = 1;
			}
			if (val == 0)
				goto printzero;
			/* FALLTHRU */
		case 'U':
			/* output a long unsigned decimal number */
#ifdef	USE_LONGLONG
		prunsigned:
			if (type == 'Q')
				prldnum(llval, &fa);
			else
#endif
			prdnum(val, &fa);
			break;
		case 'O':
			/* output a long octal number */
			if (fa.flags & HASHFLG) {
				fa.prefix = "0";
				fa.prefixlen = 1;
			}
#ifdef	USE_LONGLONG
			if (type == 'Q') {
				prlonum(llval, &fa);
			} else
#endif
			{
				pronum(val & 07, &fa);
				if ((res = (val>>3) & rshiftmask(long, 3)) != 0)
					pronum(res, &fa);
			}
			break;
		case 'p':
		case 'x':
			/* output a hex long */
			if (fa.flags & HASHFLG) {
				fa.prefix = "0x";
				fa.prefixlen = 2;
			}
#ifdef	USE_LONGLONG
			if (type == 'Q')
				prlxnum(llval, &fa);
			else
#endif
			{
				prxnum(val & 0xF, &fa);
				if ((res = (val>>4) & rshiftmask(long, 4)) != 0)
					prxnum(res, &fa);
			}
			break;
		case 'P':
		case 'X':
			/* output a hex long */
			if (fa.flags & HASHFLG) {
				fa.prefix = "0X";
				fa.prefixlen = 2;
			}
#ifdef	USE_LONGLONG
			if (type == 'Q')
				prlXnum(llval, &fa);
			else
#endif
			{
				prXnum(val & 0xF, &fa);
				if ((res = (val>>4) & rshiftmask(long, 4)) != 0)
					prXnum(res, &fa);
			}
			break;
		case 'Z':
			/* output a binary long */
#ifdef	USE_LONGLONG
			if (type == 'Q')
				prlnum(llval, 2, &fa);
			else
#endif
			{
				prnum(val & 0x1, 2, &fa);
				if ((res = (val>>1) & rshiftmask(long, 1)) != 0)
					prnum(res, 2, &fa);
			}
		}
		fa.lzero = -1;
		/*
		 * If a precision (fielwidth) is specified
		 * on diouXx conversions, the '0' flag is ignored.
		 */
		if (fa.signific >= 0)
			fa.fillc = ' ';
		count += prbuf(fa.bufp, &fa);
	}
	return (count);
}

/*
 * Routines to print (not negative) numbers in an arbitrary base
 */
LOCAL	unsigned char	dtab[]  = "0123456789abcdef";
LOCAL	unsigned char	udtab[] = "0123456789ABCDEF";

LOCAL void
prnum(val, base, fa)
	register Ulong val;
	register unsigned base;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[modlbys(val, base)];
		val = divlbys(val, base);
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prdnum(val, fa)
	register Ulong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[modlbys(val, (unsigned)10)];
		val = divlbys(val, (unsigned)10);
	} while (val > 0);

	fa->bufp = p;
}

/*
 * We may need to use division here too (PDP-11, non two's complement ...)
 */
LOCAL void
pronum(val, fa)
	register Ulong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[val & 7];
		val >>= 3;
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prxnum(val, fa)
	register Ulong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[val & 15];
		val >>= 4;
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prXnum(val, fa)
	register Ulong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = udtab[val & 15];
		val >>= 4;
	} while (val > 0);

	fa->bufp = p;
}

#ifdef	USE_LONGLONG
LOCAL void
prlnum(val, base, fa)
	register Ullong val;
	register unsigned base;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[modlbys(val, base)];
		val = divlbys(val, base);
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prldnum(val, fa)
	register Ullong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[val % (unsigned)10];
		val = val / (unsigned)10;
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prlonum(val, fa)
	register Ullong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[val & 7];
		val >>= 3;
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prlxnum(val, fa)
	register Ullong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = dtab[val & 15];
		val >>= 4;
	} while (val > 0);

	fa->bufp = p;
}

LOCAL void
prlXnum(val, fa)
	register Ullong val;
	f_args *fa;
{
	register char *p = fa->bufp;

	do {
		*--p = udtab[val & 15];
		val >>= 4;
	} while (val > 0);

	fa->bufp = p;
}

#endif

/*
 * Final buffer print out routine.
 */
LOCAL int
prbuf(s, fa)
	register const char *s;
	f_args *fa;
{
	register int diff;
	register int rfillc;
	register long arg			= fa->farg;
	register void (*fun) __PR((char, long))	= fa->outf;
	register int count;
	register int lzero = 0;

	count = strlen(s);

	/*
	 * lzero becomes the number of left fill chars needed to reach signific
	 */
	if (fa->lzero < 0 && count < fa->signific)
		lzero = fa->signific - count;
	count += lzero + fa->prefixlen;
	diff = fa->fldwidth - count;
	if (diff > 0)
		count += diff;

	if (fa->prefixlen && fa->fillc != ' ') {
		while (*fa->prefix != '\0')
			(*fun)(*fa->prefix++, arg);
	}
	if (!fa->minusflag) {
		rfillc = fa->fillc;
		while (--diff >= 0)
			(*fun)(rfillc, arg);
	}
	if (fa->prefixlen && fa->fillc == ' ') {
		while (*fa->prefix != '\0')
			(*fun)(*fa->prefix++, arg);
	}
	if (lzero > 0) {
		rfillc = '0';
		while (--lzero >= 0)
			(*fun)(rfillc, arg);
	}
	while (*s != '\0')
		(*fun)(*s++, arg);
	if (fa->minusflag) {
		rfillc = ' ';
		while (--diff >= 0)
			(*fun)(rfillc, arg);
	}
	return (count);
}

/*
 * Print out one char, allowing prc('\0')
 * Similar to prbuf()
 */
#ifdef	PROTOTYPES

LOCAL int
prc(char c, f_args *fa)

#else

LOCAL int
prc(c, fa)
	char	c;
	f_args *fa;
#endif
{
	register int diff;
	register int rfillc;
	register long arg			= fa->farg;
	register void (*fun) __PR((char, long))	= fa->outf;
	register int count;

	count = 1;
	diff = fa->fldwidth - 1;
	if (diff > 0)
		count += diff;

	if (!fa->minusflag) {
		rfillc = fa->fillc;
		while (--diff >= 0)
			(*fun)(rfillc, arg);
	}
	(*fun)(c, arg);
	if (fa->minusflag) {
		rfillc = ' ';
		while (--diff >= 0)
			(*fun)(rfillc, arg);
	}
	return (count);
}

/*
 * String output routine.
 * If fa->signific is >= 0, it uses only fa->signific chars.
 * If fa->signific is 0, print no characters.
 */
LOCAL int
prstring(s, fa)
	register const char	*s;
	f_args *fa;
{
	register char	*bp;
	register int	signific;

	if (s == NULL)
		return (prbuf("(NULL POINTER)", fa));

	if (fa->signific < 0)
		return (prbuf(s, fa));

	bp = fa->buf;
	signific = fa->signific;

	while (--signific >= 0 && *s != '\0')
		*bp++ = *s++;
	*bp = '\0';

	return (prbuf(fa->buf, fa));
}

#ifdef	DEBUG
LOCAL void
dbg_print(fmt, a, b, c, d, e, f, g, h, i)
char *fmt;
{
	char	ff[1024];

	sprintf(ff, fmt, a, b, c, d, e, f, g, h, i);
	write(STDERR_FILENO, ff, strlen(ff));
}
#endif
