/* @(#)format.c	1.73 18/11/21 Copyright 1985-2018 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-2018 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.
 * A copy of the CDDL is also available via the Internet at
 * http://www.opensource.org/licenses/cddl1.txt
 *
 * 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>
#include <schily/ctype.h>
#include "format.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.
 */
#ifndef	USE_LONGLONG
#define	USE_LONGLONG
#endif

#ifdef	NO_LONGLONG
#undef	USE_LONGLONG
#endif

#ifndef	USE_NL_ARGS
#define	USE_NL_ARGS
#endif

#ifdef	NO_NL_ARGS
#undef	USE_NL_ARGS
#endif

/*
 * Avoid to keep copies of the variable arg list in case that
 * format() was compiled without including NL support for
 * argument reordering.
 */
#ifdef	USE_NL_ARGS
#define	args		fargs.ap	/* Use working copy */
#else
#define	args		oargs		/* Directly use format() arg */
#endif

/*
 * We may need to decide whether we should check whether all
 * flags occur according to the standard which is either directly past:
 * "%" or directly past "%n$".
 *
 * This however may make printf() slower in some cases.
 */
#ifdef	USE_CHECKFLAG
#define	CHECKFLAG()	if (fa.flags & GOTSTAR) goto flagerror
#else
#define	CHECKFLAG()
#endif

#ifdef	NO_USER_XCVT
	/*
	 * We cannot define our own gcvt() so we need to use a
	 * local name instead.
	 */
#ifndef	HAVE_GCVT
#	define	gcvt	js_gcvt
EXPORT	char *gcvt	__PR((double value, int ndigit, char *buf));
#endif
#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 {
#ifdef	FORMAT_BUFFER
#define	BFSIZ	256
	char	*ptr;			/* Current ptr in buf		*/
	int	cnt;			/* Free char count in buf	*/
#else
	void  (*outf)__PR((char, void *)); /* Func from format(fun, arg)	*/
#endif
	void	*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')	*/
	ssize_t	maxlen;
#ifdef	FORMAT_BUFFER
					/* rarely used members last:	*/
	char	iobuf[BFSIZ];		/* buffer for stdio		*/
	FILE	*fp;			/* FILE * for fprformat()	*/
	int	err;			/* FILE * I/O error		*/
#endif
} f_args;

#define	FMT_ARGMAX	30	/* Number of fast args */

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	USE_NL_ARGS
#ifndef	FORMAT_FUNC_NAME
#define	FORMAT_IMPL
EXPORT	void	_fmtarglist __PR((const char *fmt, va_lists_t, va_lists_t arglist[]));
EXPORT	void	_fmtgetarg  __PR((const char *fmt, int num, va_lists_t *));
#else
extern	void	_fmtarglist __PR((const char *fmt, va_lists_t, va_lists_t arglist[]));
extern	void	_fmtgetarg  __PR((const char *fmt, int num, va_lists_t *));
#endif
#endif

#ifdef	FORMAT_BUFFER
LOCAL char	xflsbuf	__PR((int c, f_args *ap));

LOCAL char
xflsbuf(c, ap)
	int	c;
	f_args	*ap;
{
	*ap->ptr++ = c;
	if (filewrite((FILE *)ap->fp, ap->iobuf, ap->ptr - ap->iobuf) < 0)
		ap->err = 1;

	ap->cnt = BFSIZ;
	ap->ptr = ap->iobuf;
	return (c);
}

#undef	ofun
#define	ofun(c, xp)		(--((f_args *)xp)->cnt <= 0 ? \
					xflsbuf(c, (f_args *)xp) : \
					(*(((f_args *)xp)->ptr)++ = (c)))

#endif

#ifndef	FORMAT_FUNC_NAME
#define	FORMAT_FUNC_NAME	format
#define	FORMAT_FUNC_PARM

#define	FORMAT_FUNC_PROTO_DECL	void (*fun)(char, void *),
#define	FORMAT_FUNC_KR_DECL	register void (*fun)();
#define	FORMAT_FUNC_KR_ARGS	fun,

#define	ofun(c, fp)		(*fun)(c, fp)
#endif

#ifdef	FORMAT_BUFFER
#define	FARG		((void *)((UIntptr_t)farg|1))
#else
#define	FARG		farg
#endif

#ifdef	PROTOTYPES
EXPORT int
FORMAT_FUNC_NAME(FORMAT_FUNC_PROTO_DECL
			void *farg,
			const char *fmt,
			va_list oargs)
#else
EXPORT int
FORMAT_FUNC_NAME(FORMAT_FUNC_KR_ARGS farg, fmt, oargs)
	FORMAT_FUNC_KR_DECL
	register void	*farg;
	register char	*fmt;
	va_list		oargs;
#endif
{
#ifdef	FORMAT_LOW_MEM
#define	FORMAT_BSIZE	512
	char buf[512+1];
#else
#define	FORMAT_BSIZE	8192
	char buf[8192+1];
#endif
	const char *sfmt;
	register int unsflag;
	register long val;
	register char type;
	register char mode;
	register char c;
	int count;
	int num;
	int i;
	short sh;
	const char *str;
	double dval;
#ifdef	USE_LONGLONG
	Llong llval = 0;
#endif
	Ulong res;
	char *rfmt;
	f_args	fa;
#ifdef	USE_NL_ARGS
	va_lists_t	fargs;		/* Used to get arguments */
	va_lists_t	sargs;		/* Saved argument state */
	va_lists_t	arglist[FMT_ARGMAX+1]; /* List of fast args */
	const char 	*ofmt = fmt;	/* Saved original format */
	BOOL		didlist = FALSE; /* Need to scan arguments */
#endif

#ifdef	FORMAT_BUFFER
	if (((UIntptr_t)farg & 1) == 0) { /* Called externally	*/
		fa.cnt	= BFSIZ;
		fa.ptr	= fa.iobuf;
		fa.fp	= (FILE *)farg;
		fa.err	= 0;
		farg	= fa.farg = &fa;
	} else {			/* recursion		*/
		farg = (void *)((UIntptr_t)farg & ~1);
	}
#endif
#ifdef	FORMAT_FUNC_PARM
	fa.outf = fun;
#endif
	fa.farg = farg;
	count = 0;

#ifdef	USE_NL_ARGS
	va_copy(sargs.ap, oargs);	/* Keep a copy in sargs */
	fargs = sargs;			/* Make a working copy  */
#endif

	/*
	 * 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')
				goto out;
			ofun(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;
		fa.maxlen = -1;
		sfmt = fmt;
		unsflag = FALSE;
		type = '\0';
		mode = '\0';
		/*
		 * %<flags>f.s<length-mod><conversion-spec>
		 * %<flags>*.*<length-mod><conversion-spec>
		 * %n$<flags>f.s<length-mod><conversion-spec>
		 * %n$<flags>*n$.*n$<length-mod><conversion-spec>
		 */
	newflag:
		switch (*(++fmt)) {

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

		case '-':
			CHECKFLAG();
			fa.minusflag++;
			fa.flags |= MINUSFLG;
			goto newflag;

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

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

		case '\'':
			CHECKFLAG();
			fa.flags |= APOFLG;
			goto newflag;

		case '.':
			fa.flags |= GOTDOT;
			fa.signific = 0;
			goto newflag;

		case '*':
			fa.flags |= GOTSTAR;
#ifdef	USE_NL_ARGS
			if (is_dig(fmt[1])) {	/* *n$ */
				fmt++;		/* Eat up '*' */
				goto dodig;
			}
#endif
			if (!(fa.flags & GOTDOT)) {
				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.flags |= MINUSFLG;
				}
			} else {
				/*
				 * A negative significance (precision) is taken
				 * as if the precision and '.' were omitted.
				 */
				fa.signific = va_arg(args, int);
				if (fa.signific < 0)
					fa.signific = -1;
			}
			goto newflag;

		case '0':
			/*
			 * '0' may be a flag.
			 */
			if (!(fa.flags & (GOTDOT | GOTSTAR | MINUSFLG))) {
				fa.fillc = '0';
				fa.flags |= PADZERO;
			}
			/* FALLTHRU */
		case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
#ifdef	USE_NL_ARGS
		dodig:
#endif
			num = *fmt++ - '0';
			while (c = *fmt, is_dig(c)) {
				num *= 10;
				num += c - '0';
				fmt++;
			}
#ifdef	USE_NL_ARGS
			if (c == '$')
				goto doarglist;
#endif
			fmt--;			/* backup to last digit */
			if (!(fa.flags & GOTDOT))
				fa.fldwidth = num;
			else
				fa.signific = num;
			goto newflag;

#ifdef	USE_NL_ARGS
		doarglist:
			{
			va_lists_t	tmp;	/* Temporary arg state */
			if (num <= 0)		/* Illegal arg offset */
				goto newflag;	/* Continue after '$' */
			if (!didlist) {		/* Need to init arglist */
				_fmtarglist(ofmt, sargs, arglist);
				didlist = TRUE;
			}
			if (num <= FMT_ARGMAX) {
				tmp = arglist[num-1];
			} else {
				tmp = arglist[FMT_ARGMAX-1];
				_fmtgetarg(ofmt, num, &tmp);
			}
			if (!(fa.flags & GOTSTAR)) {
				fargs = tmp;
			} else {
				if (!(fa.flags & GOTDOT)) {
					fa.fldwidth = va_arg(tmp.ap, int);
					/*
					 * A negative fieldwith is a minus flag
					 * with a positive fieldwidth.
					 */
					if (fa.fldwidth < 0) {
						fa.fldwidth = -fa.fldwidth;
						fa.minusflag = 1;
						fa.flags |= MINUSFLG;
					}
				} else {
					/*
					 * A negative significance (precision)
					 * is taken as if the precision and '.'
					 * were omitted.
					 */
					fa.signific = va_arg(tmp.ap, int);
					if (fa.signific < 0)
						fa.signific = -1;
				}
			}
			goto newflag;
			}
#endif

#ifdef	USE_CHECKFLAG
		flagerror:
			fmt = ++sfmt;		/* Don't print '%'   */
			continue;
#endif
		}
		/*
		 * If the space and the + flag are present,
		 * the space flag will be ignored.
		 */
		if ((fa.flags & (PLUSFLG|SPACEFLG)) == (PLUSFLG|SPACEFLG))
			fa.flags &= ~SPACEFLG;
		/*
		 * If the 0 and the - flag are present,
		 * the 0 flag will be ignored.
		 */
		if ((fa.flags & (MINUSFLG|PADZERO)) == (MINUSFLG|PADZERO))
			fa.flags &= ~PADZERO;

		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
				 */
				type = *fmt++;
				if (!strchr("ZODX", mode = *fmt)) {
					/*
					 * Check long double "Le", "Lf" or "Lg"
					 */
					if (type == 'L' &&
					    (mode == 'e' || mode == 'E' ||
					    mode == 'f' || mode == 'F' ||
					    mode == 'g' || mode == 'G'))
						goto checkfmt;
					fmt--;
					mode = 'D'; /* default mode */
				}
			}
		} else {
	checkfmt:
		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 */
			goto getmode;

		case 'z':			/* size_t */
#if	SIZEOF_SIZE_T == SIZEOF_INT
			if (!type)
				type = 'I';	/* convert to int type */
#else
#if	SIZEOF_SIZE_T == SIZEOF_LONG_INT
			if (!type)
				type = 'L';	/* convert to long type */
#else
#if	SIZEOF_SIZE_T == SIZEOF_LLONG
			if (!type)
				type = 'Q';	/* convert to long long type */
#else
error sizeof (size_t) is unknown
#endif
#endif
#endif
			goto getmode;

		case 't':			/* ptrdiff_t */
#if	SIZEOF_PTRDIFF_T == SIZEOF_INT
			if (!type)
				type = 'I';	/* convert to int type */
#else
#if	SIZEOF_PTRDIFF_T == SIZEOF_LONG_INT
			if (!type)
				type = 'L';	/* convert to long type */
#else
#if	SIZEOF_PTRDIFF_T == SIZEOF_LLONG
			if (!type)
				type = 'Q';	/* convert to long long type */
#else
error sizeof (ptrdiff_t) is unknown
#endif
#endif
#endif
		/*
		 * XXX Future length modifiers:
		 * XXX	'L' with double: long double
		 */

		getmode:
			if (!strchr("udioxXn", *(++fmt))) {
				/*
				 * %hhd -> char in decimal
				 */
				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 {		/* One of "udioxXn": */
				mode = *fmt;
				if (mode == 'n')
					goto gotn;
				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':
			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':
		case 'E': {
			int	signific;
			int	fwidth = 0;

			if (fa.signific == -1)
				fa.signific = 6;
			signific = fa.signific;
			if (!fa.minusflag)
				fwidth = fa.fldwidth;
			if (*fmt == 'E')
				fa.flags |= UPPERFLG;
			if (type == 'L') {
#ifdef	HAVE_LONGDOUBLE
				long double ldval = va_arg(args, long double);

#if	(defined(HAVE_QECVT) || defined(HAVE__LDECVT))
				_qftoes(buf, ldval, fwidth, signific, fa.flags);
				fa.fillc = ' ';
				count += prbuf(buf, &fa);
				continue;
#else
				dval = ldval;
#endif
#endif
			} else {
				dval = va_arg(args, double);
			}
			_ftoes(buf, dval, fwidth, signific, fa.flags);
			fa.fillc = ' ';
			count += prbuf(buf, &fa);
			continue;
			}
		case 'f':
		case 'F': {
			int	signific;
			int	fwidth = 0;

			if (fa.signific == -1)
				fa.signific = 6;
			signific = fa.signific;
			if (!fa.minusflag)
				fwidth = fa.fldwidth;
			if (*fmt == 'F')
				fa.flags |= UPPERFLG;
			if (type == 'L') {
#ifdef	HAVE_LONGDOUBLE
				long double ldval = va_arg(args, long double);

#if	(defined(HAVE_QFCVT) || defined(HAVE__LDFCVT))
				_qftofs(buf, ldval, fwidth, signific, fa.flags);
				fa.fillc = ' ';
				count += prbuf(buf, &fa);
				continue;
#else
				dval = ldval;
#endif
#endif
			} else {
				dval = va_arg(args, double);
			}
			_ftofs(buf, dval, fwidth, signific, fa.flags);
			fa.fillc = ' ';
			count += prbuf(buf, &fa);
			continue;
			}
		case 'g':
		case 'G': {
			int	signific;
			int	fwidth = 0;

			if (fa.signific == -1)
				fa.signific = 6;
			if (fa.signific == 0)
				fa.signific = 1;
			signific = fa.signific;
			if (!fa.minusflag)
				fwidth = fa.fldwidth;
			if (*fmt == 'G')
				fa.flags |= UPPERFLG;
			if (type == 'L') {
#ifdef	HAVE_LONGDOUBLE
				long double ldval = va_arg(args, long double);

#if	(defined(HAVE_QECVT) || defined(HAVE__LDECVT))
				_qftogs(buf, ldval, fwidth, signific, fa.flags);
				fa.fillc = ' ';
				count += prbuf(buf, &fa);
				continue;
#else
				dval = ldval;
#endif
#endif
			} else {
				dval = va_arg(args, double);
			}
			_ftogs(buf, dval, fwidth, signific, fa.flags);
			fa.fillc = ' ';
			count += prbuf(buf, &fa);
			continue;
			}
#else
#	ifdef	USE_FLOATINGARGS
		case 'e':
		case 'E':
		case 'f':
		case 'F':
		case 'g':
		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_FUNC_NAME(FORMAT_FUNC_KR_ARGS
					FARG, rfmt, __va_arg_list(args));
			continue;

		gotn:
		case 'n':
			switch (type) {

			case 'C': {
				signed char *cp = va_arg(args, signed char *);

				*cp = count;
				}
				continue;
			case 'H': {
				short	*sp = va_arg(args, short *);

				*sp = count;
				}
				continue;
			case 'L': {
				long	*lp = va_arg(args, long *);

				*lp = count;
				}
				continue;
#ifdef	USE_LONGLONG
			case 'J':		/* For now Intmax_t is Llong */
			case 'Q': {
				Llong *qp = va_arg(args, Llong *);

				*qp = count;
				}
				continue;
#endif
			default: {
				int	*ip = va_arg(args, int *);

				*ip = count;
				}
				continue;
			}

		default:			/* Unknown '%' format */
			sfmt++;			/* Dont't print '%'   */
			count += fmt - sfmt;
			while (sfmt < fmt)
				ofun(*(sfmt++), farg);
			if (*fmt == '\0') {
				fmt--;
				continue;
			} else {
				ofun(*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 */
			type = 'Q';		/* use 'Q' for processing    */
		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 */

			char	*p = fa.bufp;
#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);
			}
			if ((fa.flags & HASHFLG) &&
			    (p - fa.bufp) >= fa.signific) {
				/*
				 * Add '0' only if not left zero filled
				 * otherwise.
				 */
				fa.prefix = "0";
				fa.prefixlen = 1;
			}
			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);
	}
out:
#ifdef	FORMAT_BUFFER
	if (farg == &fa) {		/* Top level call, flush buffer */
		if (fa.err)
			return (EOF);
		if ((fa.ptr != fa.iobuf) &&
		    (filewrite(fa.fp, fa.iobuf, fa.ptr - fa.iobuf) < 0))
			return (EOF);
	}
#endif
	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 void *arg				= fa->farg;
#ifdef	FORMAT_FUNC_PARM
	register void (*fun) __PR((char, void *))	= fa->outf;
#endif
	register int count;
	register int lzero = 0;

	if (fa->maxlen < 0)
		count = strlen(s);
	else
		count = fa->maxlen;

	/*
	 * 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')
			ofun(*fa->prefix++, arg);
	}
	if (!fa->minusflag) {
		rfillc = fa->fillc;
		while (--diff >= 0)
			ofun(rfillc, arg);
	}
	if (fa->prefixlen && fa->fillc == ' ') {
		while (*fa->prefix != '\0')
			ofun(*fa->prefix++, arg);
	}
	if (lzero > 0) {
		rfillc = '0';
		while (--lzero >= 0)
			ofun(rfillc, arg);
	}
	if (fa->maxlen < 0) {
		while (*s != '\0')
			ofun(*s++, arg);
	} else {
		register	size_t	len = fa->maxlen;

		if (len > 0) {
			len++;
			while (--len > 0)
				ofun(*s++, arg);
		}
	}
	if (fa->minusflag) {
		rfillc = ' ';
		while (--diff >= 0)
			ofun(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 void *arg				= fa->farg;
#ifdef	FORMAT_FUNC_PARM
	register void (*fun) __PR((char, void *))	= fa->outf;
#endif
	register int count;

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

	if (!fa->minusflag) {
		rfillc = fa->fillc;
		while (--diff >= 0)
			ofun(rfillc, arg);
	}
	ofun(c, arg);
	if (fa->minusflag) {
		rfillc = ' ';
		while (--diff >= 0)
			ofun(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;
{
	if (s == NULL)
		return (prbuf("(NULL POINTER)", fa));

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

	fa->maxlen = strnlen(s, fa->signific);
	return (prbuf(s, 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

#ifdef	USE_NL_ARGS
#ifdef	FORMAT_IMPL
/*
 * The following code is shared between format() and fprformat().
 */

/*
 * Format argument types.
 * As "char" and "short" type arguments are fetched as "int"
 * we start with size "int" and ignore the 'h' modifier when
 * parsing sizes.
 */
#define	AT_NONE			0
#define	AT_INT			1
#define	AT_LONG			2
#define	AT_LONG_LONG		3
#define	AT_DOUBLE		4
#define	AT_LONG_DOUBLE		5
#define	AT_VOID_PTR		6
#define	AT_CHAR_PTR		7
#define	AT_SHORT_PTR		8
#define	AT_INT_PTR		9
#define	AT_LONG_PTR		10
#define	AT_LONG_LONG_PTR	11
#define	AT_R_FMT		12
#define	AT_R_VA_LIST		13
#define	AT_BOUNDS		14

#define	AF_NONE			0
#define	AF_LONG			1
#define	AF_LONG_LONG		2
#define	AF_LONG_DOUBLE		4
#define	AF_STAR			8

static	const char	skips[] = "+- #'.$h1234567890";
static	const char	*digits = &skips[8];

/*
 * Parse the format string and store the first FMT_ARGMAX args in the arglist
 * parameter.
 *
 * This is done in two stages:
 *	1	parse the format string and store the types in argtypes[].
 *	2	use the type list in argtypes[], fetch the args in order and
 *		store the related va_list state in arglist[]
 */
EXPORT void
_fmtarglist(fmt, fargs, arglist)
	const char	*fmt;
	va_lists_t	fargs;
	va_lists_t	arglist[];
{
	int	i;
	int	argindex;
	int	maxindex;
	int	thistype;
	int	thisflag;
	int	argtypes[FMT_ARGMAX+1];

	for (i = 0; i < FMT_ARGMAX; i++)
		argtypes[i] = AT_NONE;

	maxindex = -1;
	argindex = 0;
	while ((fmt = strchr(fmt, '%')) != NULL) {
		fmt++;
		i = strspn(fmt, digits);
		if (fmt[i] == '$') {
			int	c;

			argindex = *fmt++ - '0';
			while (c = *fmt, is_dig(c)) {
				argindex *= 10;
				argindex += c - '0';
				fmt++;
			}
			argindex -= 1;
		}
		thistype = AT_NONE;
		thisflag = AF_NONE;
	newarg:
		fmt += strspn(fmt, skips);
		switch (*fmt++) {

		case '%':		/* %% format no arg */
			continue;

		case 'l':
			if (thisflag & AF_LONG) {
				thisflag |= AF_LONG_LONG;
			} else {
				thisflag |= AF_LONG;
			}
			goto newarg;
		case 'j':		/* intmax_t for now is long long */
			thisflag |= AF_LONG_LONG;
			goto newarg;
		case 'z':		/* size_t */
#if	SIZEOF_SIZE_T == SIZEOF_INT
			if (thistype == AT_NONE)
				thistype = AT_INT;
#else
#if	SIZEOF_SIZE_T == SIZEOF_LONG_INT
			if (thistype == AT_NONE)
				thistype = AT_LONG;
#else
#if	SIZEOF_SIZE_T == SIZEOF_LLONG
			if (thistype == AT_NONE)
				thistype = AT_LONG_LONG;
#else
error sizeof (size_t) is unknown
#endif
#endif
#endif
			goto newarg;
		case 't':		/* ptrdiff_t */
#if	SIZEOF_PTRDIFF_T == SIZEOF_INT
			if (thistype == AT_NONE)
				thistype = AT_INT;
#else
#if	SIZEOF_PTRDIFF_T == SIZEOF_LONG_INT
			if (thistype == AT_NONE)
				thistype = AT_LONG;
#else
#if	SIZEOF_PTRDIFF_T == SIZEOF_LLONG
			if (thistype == AT_NONE)
				thistype = AT_LONG_LONG;
#else
error sizeof (ptrdiff_t) is unknown
#endif
#endif
#endif
			goto newarg;
#ifndef	NO_UCSIL
			/*
			 * Enhancements to K&R and ANSI:
			 *
			 * got a type specifyer
			 *
			 * XXX 'S' in C99 is %ls, 'S' should become 'H'
			 */
		case 'U':
			if (!strchr("CSILZODX", *fmt)) {
				/*
				 * Got only 'U'nsigned specifyer,
				 * use default type and mode.
				 */
				thistype = AT_INT;
				break;
			}
			if (!strchr("CSIL", *fmt)) {
				/*
				 * Got 'U' and ZODX.
				 * no type, use default
				 */
				thistype = AT_INT;
				fmt++;	/* Skip ZODX */
				break;
			}
			fmt++;		/* Unsigned, skip 'U', get CSIL */
			/* FALLTHRU */
		case 'C':
		case 'S':
		case 'I':
		case 'L':
			fmt--;		/* Undo fmt++ from switch() */
			{
				/*
				 * got CSIL type
				 */
				int	type = *fmt++;	/* Undo above fmt-- */
				int	mode = *fmt;
				if (!strchr("ZODX", mode)) {
					/*
					 * Check long double "Le", "Lf" or "Lg"
					 */
					if (type == 'L' &&
					    (mode == 'e' ||
					    mode == 'f' ||
					    mode == 'g')) {
						thisflag |= AF_LONG_DOUBLE;
						goto newarg;
					}
				} else {
					fmt++;	/* Skip ZODX */
				}
				if (type == 'L')
					thistype = AT_LONG;
				else
					thistype = AT_INT;
			}
			break;
#else
		case 'L':
			thisflag |= AF_LONG_DOUBLE;
			goto newarg;
#endif

		case 'e':
		case 'E':
		case 'f':
		case 'F':
		case 'g':
		case 'G':
			if (thisflag & AF_LONG_DOUBLE)
				thistype = AT_LONG_DOUBLE;
			else
				thistype = AT_DOUBLE;
			break;

		case 'p':
			thistype = AT_VOID_PTR;
			break;
		case 's':
			thistype = AT_CHAR_PTR;
			break;
		case 'b':
			thistype = AT_BOUNDS;
			break;
		case 'n':
			if (thisflag & AF_LONG_LONG)
				thistype = AT_LONG_LONG_PTR;
			else if (thistype & AF_LONG)
				thistype = AT_LONG_PTR;
			else
				thistype = AT_INT_PTR;
			break;
		case 'r':
			thistype = AT_R_FMT;
			break;
		default:
			if (thistype == AT_NONE) {
				if (thisflag & AF_LONG_LONG)
					thistype = AT_LONG_LONG;
				else if (thistype & AF_LONG)
					thistype = AT_LONG;
				else
					thistype = AT_INT;
			}
			break;

		case '*':
			if (is_dig(*fmt)) {
				int	c;
				int	starindex;

				starindex = *fmt++ - '0';
				while (c = *fmt, is_dig(c)) {
					starindex *= 10;
					starindex += c - '0';
					fmt++;
				}
				starindex -= 1;
				if (starindex >= 0 && starindex < FMT_ARGMAX) {
					argtypes[starindex] = AT_INT;
					if (starindex > maxindex)
						maxindex = starindex;
				}
				goto newarg;
			}
			thistype = AT_INT;
			thisflag |= AF_STAR; /* Make sure to rescan for type */
			break;
		}
		if (argindex >= 0 && argindex < FMT_ARGMAX) {
			argtypes[argindex] = thistype;
			if (thistype == AT_R_FMT)
				argtypes[++argindex] = AT_R_VA_LIST;
			else if (thistype == AT_BOUNDS)
				argtypes[++argindex] = AT_INT;

			if (argindex > maxindex)
				maxindex = argindex;
		}
		++argindex;		/* Default to next arg in list */
		if (thisflag & AF_STAR) { /* Found '*', continue for type */
			thisflag &= ~AF_STAR;
			goto newarg;
		}
	}

	for (i = 0; i <= maxindex; i++) { /* Do not fetch more args than known */
		arglist[i] = fargs;	/* Save state before fetching this */

		switch (argtypes[i]) {

		default:
			/* FALLTHRU */
		case AT_NONE:		/* This matches '*' args */
			/* FALLTHRU */
		case AT_INT:
			(void) va_arg(fargs.ap, int);
			break;
		case AT_LONG:
			(void) va_arg(fargs.ap, long);
			break;
		case AT_LONG_LONG:
			(void) va_arg(fargs.ap, Llong);
			break;
		case AT_DOUBLE:
			(void) va_arg(fargs.ap, double);
			break;
		case AT_LONG_DOUBLE:
#ifdef	HAVE_LONGDOUBLE
			(void) va_arg(fargs.ap, long double);
#endif
			break;
		case AT_VOID_PTR:
			(void) va_arg(fargs.ap, void *);
			break;
		case AT_CHAR_PTR:
			(void) va_arg(fargs.ap, char *);
			break;
		case AT_SHORT_PTR:
			(void) va_arg(fargs.ap, short *);
			break;
		case AT_INT_PTR:
			(void) va_arg(fargs.ap, int *);
			break;
		case AT_LONG_PTR:
			(void) va_arg(fargs.ap, long *);
			break;
		case AT_LONG_LONG_PTR:
			(void) va_arg(fargs.ap, Llong *);
			break;
		case AT_R_FMT:
			(void) va_arg(fargs.ap, char *);
			arglist[++i] = fargs;
			(void) __va_arg_list(fargs.ap);
			break;
		case AT_R_VA_LIST:
			break;
		case AT_BOUNDS:
			(void) va_arg(fargs.ap, char *);
			arglist[++i] = fargs;
			(void) va_arg(fargs.ap, int);
			break;
		}
	}
}

/*
 * In case that the format references an argument > FMT_ARGMAX, we use this
 * implementation. It is slow (n*n - where n is (argno - FMT_ARGMAX)).
 * Fortunately, it is most unlikely that there are more positional args than
 * the current FMT_ARGMAX definition of 30.
 */
EXPORT void
_fmtgetarg(fmt, num, fargs)
	const char	*fmt;
	int		num;
	va_lists_t	*fargs;
{
	const char	*sfmt = fmt;
	int		i;

	/*
	 * Hacky preliminary support for all int type args bejond FMT_ARGMAX.
	 */
	for (i = FMT_ARGMAX; i < num; i++)
		(void) va_arg((*fargs).ap, int);
}
#endif	/* FORMAT_IMPL */
#endif	/* USE_NL_ARGS */
