/*
** zstrformat.cpp
** Routines for generic printf-style formatting.
**
**---------------------------------------------------------------------------
** Copyright 2005-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
** Portions of this file relating to printing floating point numbers
** are covered by the following copyright:
**
**---------------------------------------------------------------------------
** Copyright (c) 1990, 1993
**	The Regents of the University of California.  All rights reserved.
**
** This code is derived from software contributed to Berkeley by
** Chris Torek.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 4. Neither the name of the University nor the names of its contributors
**    may be used to endorse or promote products derived from this software
**    without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
**---------------------------------------------------------------------------
**
** Even though the standard C library has a function to do printf-style
** formatting in a generic way, there is no standard interface to this
** function. So if you want to do some printf formatting that doesn't fit in
** the context of the provided functions, you need to roll your own. Why is
** that?
**
** Maybe Microsoft wants you to write a better one yourself? When used as
** part of a sprintf replacement, this function is significantly faster than
** Microsoft's offering. When used as part of a fprintf replacement, this
** function turns out to be slower, but that's probably because the CRT's
** fprintf can interact with the FILE object on a low level for better
** perfomance. If you sprintf into a buffer and then fwrite that buffer, this
** routine wins again, though the difference isn't great.
*/

#include <limits.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <locale.h>

#include "zstring.h"
#include "gdtoa.h"
#include "utf8.h"


/*
 * MAXEXPDIG is the maximum number of decimal digits needed to store a
 * floating point exponent in the largest supported format.  It should
 * be ceil(log10(LDBL_MAX_10_EXP)) or, if hexadecimal floating point
 * conversions are supported, ceil(log10(LDBL_MAX_EXP)).  But since it
 * is presently never greater than 5 in practice, we fudge it.
 */
#define	MAXEXPDIG	6
#if LDBL_MAX_EXP > 999999
#error "floating point buffers too small"
#endif

#define DEFPREC		6

static const char hexits[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
static const char HEXits[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static const char spaces[16] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
static const char zeroes[17] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','.'};

namespace StringFormat
{
	static int writepad (OutputFunc output, void *outputData, const char *pad, int padsize, int spaceToFill);
	static int printandpad (OutputFunc output, void *outputData, const char *p, const char *ep, int len, const char *with, int padsize);
	static int exponent (char *p0, int exp, int fmtch);

	int Worker (OutputFunc output, void *outputData, const char *fmt, ...)
	{
		va_list arglist;
		int len;

		va_start (arglist, fmt);
		len = VWorker (output, outputData, fmt, arglist);
		va_end (arglist);
		return len;
	}

	int VWorker (OutputFunc output, void *outputData, const char *fmt, va_list arglist)
	{
		const char *c;
		const char *base;
		int len = 0;
		int width;
		int precision;
		int flags;

		base = c = fmt;
		for (;;)
		{
			while (*c && *c != '%')
			{
				++c;
			}
			if (*c == '\0')
			{
				return len + output (outputData, base, int(c - base));
			}

			if (c - base > 0)
			{
				len += output (outputData, base, int(c - base));
			}
			c++;

			// Gather the flags, if any
			for (flags = 0;; ++c)
			{
				if (*c == '-')
				{
					flags |= F_MINUS;		// bit 0
				}
				else if (*c == '+')
				{
					flags |= F_PLUS;		// bit 1
				}
				else if (*c == '0')
				{
					flags |= F_ZERO;		// bit 2
				}
				else if (*c == ' ')
				{
					flags |= F_BLANK;		// bit 3
				}
				else if (*c == '#')
				{
					flags |= F_HASH;		// bit 4
				}
				else
				{
					break;
				}
			}

			width = precision = -1;

			// Read the width, if any
			if (*c == '*')
			{
				++c;
				width = va_arg (arglist, int);
				if (width < 0)
				{ // Negative width means minus flag and positive width
					flags |= F_MINUS;
					width = -width;
				}
			}
			else if (*c >= '0' && *c <= '9')
			{
				width = *c++ - '0';
				while (*c >= '0' && *c <= '9')
				{
					width = width * 10 + *c++ - '0';
				}
			}

			// If 0 and - both appear, 0 is ignored.
			// If the blank and + both appear, the blank is ignored.
			flags &= ~((flags & 3) << 2);

			// Read the precision, if any
			if (*c == '.')
			{
				precision = 0;
				if (*++c == '*')
				{
					++c;
					precision = va_arg (arglist, int);
				}
				else if (*c >= '0' && *c <= '9')
				{
					precision = *c++ - '0';
					while (*c >= '0' && *c <= '9')
					{
						precision = precision * 10 + *c++ - '0';
					}
				}
			}

			// Read the size prefix, if any
			if (*c == 'h')
			{
				if (*++c == 'h')
				{
					flags |= F_HALFHALF;
					++c;
				}
				else
				{
					flags |= F_HALF;
				}
			}
			else if (*c == 'l')
			{
				if (*++c == 'l')
				{
					flags |= F_LONGLONG;
					++c;
				}
				else
				{
					flags |= F_LONG;
				}
			}
			else if (*c == 'I')
			{
				if (*++c == '6')
				{
					if (*++c == '4')
					{
						flags |= F_LONGLONG;
                        ++c;
					}
				}
				else
				{
					flags |= F_BIGI;
				}
			}
			else if (*c == 't')
			{
				flags |= F_PTRDIFF;
				++c;
			}
			else if (*c == 'z')
			{
				flags |= F_SIZE;
				++c;
			}

			base = c+1;

			// Now that that's all out of the way, we should be pointing at the type specifier
	{
		char prefix[3];
		int prefixlen;
		char hexprefix = '\0';
		char sign = '\0';
		int postprefixzeros = 0;
		int size = flags & 0xF000;
		char buffer[80], *ibuff;
		const char *obuff = 0;
		char type = *c++;
		int bufflen = 0;
		int outlen = 0;
		unsigned int intarg = 0;
		uint64_t int64arg = 0;
		const void *voidparg;
		const char *charparg;
		double dblarg;
		const char *xits = hexits;
		int inlen = len;
		/*
		 * We can decompose the printed representation of floating
		 * point numbers into several parts, some of which may be empty:
		 *
		 * [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ
		 *    A       B     ---C---      D       E   F
		 *
		 * A:	'sign' holds this value if present; '\0' otherwise
		 * B:	hexprefix holds the 'x' or 'X'; '\0' if not hexadecimal
		 * C:	obuff points to the string MMMNNN.  Leading and trailing
		 *		zeros are not in the string and must be added.
		 * D:	expchar holds this character; '\0' if no exponent, e.g. %f
		 * F:	at least two digits for decimal, at least one digit for hex
		 */
		const char *decimal_point = ".";/* locale specific decimal point */
		int signflag;					/* true if float is negative */
		int expt;						/* integer value of exponent */
		char expchar = 'e';				/* exponent character: [eEpP\0] */
		char *dtoaend;					/* pointer to end of converted digits */
		int expsize = 0;				/* character count for expstr */
		int ndig = 0;					/* actual number of digits returned by dtoa */
		char expstr[MAXEXPDIG+2];		/* buffer for exponent string: e+ZZZ */
		char *dtoaresult = NULL;		/* buffer allocated by dtoa */

		// Using a bunch of if/else if statements is faster than a switch, because a switch generates
		// a jump table. A jump table means a possible data cache miss and a hefty penalty while the
		// cache line is loaded.

		if (type == 'x' || type == 'X' ||
			type == 'p' ||
			type == 'd' || type == 'u' || type == 'i' ||
			type == 'o' ||
			type == 'B')
		{
			if (type == 'X' || type == 'p')
			{
				xits = HEXits;
			}
			if (type == 'p')
			{
				type = 'X';
				voidparg = va_arg (arglist, void *);
				if (sizeof(void*) == sizeof(int))
				{
					intarg = (unsigned int)(size_t)voidparg;
					precision = 8;
					size = 0;
				}
				else
				{
					int64arg = (uint64_t)(size_t)voidparg;
					precision = 16;
					size = F_LONGLONG;
				}
			}
			else
			{
				if (size == 0)
				{
					intarg = va_arg (arglist, int);
				}
				else if (size == F_HALFHALF)
				{
					intarg = va_arg (arglist, int);
					intarg = (signed char)intarg;
				}
				else if (size == F_HALF)
				{
					intarg = va_arg (arglist, int);
					intarg = (short)intarg;
				}
				else if (size == F_LONG)
				{
					if (sizeof(long) == sizeof(int)) intarg = va_arg (arglist, int);
					else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
				}
				else if (size == F_BIGI)
				{
					if (sizeof(void*) == sizeof(int)) intarg = va_arg (arglist, int);
					else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
				}
				else if (size == F_LONGLONG)
				{
					int64arg = va_arg (arglist, int64_t);
				}
				else if (size == F_PTRDIFF)
				{
					if (sizeof(ptrdiff_t) == sizeof(int)) intarg = va_arg (arglist, int);
					else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
				}
				else if (size == F_SIZE)
				{
					if (sizeof(size_t) == sizeof(int)) intarg = va_arg (arglist, int);
					else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
				}
				else
				{
					intarg = va_arg (arglist, int);
				}
			}

			if (precision < 0) precision = 1;

			ibuff = &buffer[sizeof(buffer)];

			if (size == F_LONGLONG)
			{
				if (int64arg == 0)
				{
					flags |= F_ZEROVALUE;
				}
				else
				{
					if (type == 'o')
					{ // Octal: Dump digits until it fits in an unsigned int
						while (int64arg > UINT_MAX)
						{
							*--ibuff = char(int64arg & 7) + '0'; int64arg >>= 3;
						}
						intarg = int(int64arg);
					}
					else if (type == 'x' || type == 'X')
					{ // Hexadecimal: Dump digits until it fits in an unsigned int
						while (int64arg > UINT_MAX)
						{
							*--ibuff = xits[int64arg & 15]; int64arg >>= 4;
						}
						intarg = int(int64arg);
					}
					else if (type == 'B')
					{ // Binary: Dump digits until it fits in an unsigned int
						while (int64arg > UINT_MAX)
						{
							*--ibuff = char(int64arg & 1) + '0'; int64arg >>= 1;
						}
						intarg = int(int64arg);
					}
					else
					{
						if (type != 'u')
						{
							// If a signed number is negative, set the negative flag and make it positive.
							int64_t sint64arg = (int64_t)int64arg;
							if (sint64arg < 0)
							{
								flags |= F_NEGATIVE;
								sint64arg = -sint64arg;
								int64arg = sint64arg;
							}
							flags |= F_SIGNED;
							type = 'u';
						}
						// If an unsigned int64 is too big to fit in an unsigned int, dump out
						// digits until it is sufficiently small.
						while (int64arg > INT_MAX)
						{
							*--ibuff = char(int64arg % 10) + '0'; int64arg /= 10;
						}
						intarg = (unsigned int)(int64arg);
					}
				}
			}
			else
			{
				if (intarg == 0)
				{
					flags |= F_ZEROVALUE;
				}
				else if (type == 'i' || type == 'd')
				{ // If a signed int is negative, set the negative flag and make it positive.
					signed int sintarg = (signed int)intarg;
					if (sintarg < 0)
					{
						flags |= F_NEGATIVE;
						sintarg = -sintarg;
						intarg = sintarg;
					}
					flags |= F_SIGNED;
					type = 'u';
				}
			}
			if (flags & F_ZEROVALUE)
			{
				if (precision != 0)
				{
					*--ibuff = '0';
				}
			}
			else if (type == 'u')
			{ // Decimal
				int i;

				// Unsigned division is typically slower than signed division.
				// Do it at most once.
				if (intarg > INT_MAX)
				{
					*--ibuff = char(intarg % 10) + '0'; intarg /= 10;
				}
				i = (int)intarg;
				while (i != 0)
				{
					*--ibuff = char(i % 10) + '0'; i /= 10;
				}
			}
			else if (type == 'o')
			{ // Octal
				while (intarg != 0)
				{
					*--ibuff = char(intarg & 7) + '0'; intarg >>= 3;
				}
			}
			else if (type == 'B')
			{ // Binary
				while (intarg != 0)
				{
					*--ibuff = char(intarg & 1) + '0'; intarg >>= 1;
				}
			}
			else
			{ // Hexadecimal
				while (intarg != 0)
				{
					*--ibuff = xits[intarg & 15]; intarg >>= 4;
				}
			}
			// Check for prefix (only for non-decimal, which are always unsigned)
			if ((flags & (F_HASH|F_ZEROVALUE)) == F_HASH)
			{
				if (type == 'o')
				{
					if (bufflen >= precision)
					{
						sign = '0';
					}
				}
				else if (type == 'x' || type == 'X')
				{
					hexprefix = type;
				}
				else if (type == 'B')
				{
					hexprefix = '!';
				}
			}
			bufflen = (int)(ptrdiff_t)(&buffer[sizeof(buffer)] - ibuff);
			obuff = ibuff;
			if (precision >= 0)
			{
				postprefixzeros = precision - bufflen;
				if (postprefixzeros < 0) postprefixzeros = 0;
//				flags &= ~F_ZERO;
			}
		}
		else if (type == 'c')
		{
			intarg = va_arg (arglist, int);
			if (utf8_encode(intarg, (uint8_t*)buffer, &bufflen) != 0)
			{
				buffer[0] = '?';
				bufflen = 1;
			}
			obuff = buffer;
		}
		else if (type == 's')
		{
			charparg = va_arg (arglist, const char *);
			if (charparg == NULL)
			{
				obuff = "(null)";
				bufflen = 6;
			}
			else
			{
				obuff = charparg;
				if (precision < 0)
				{
					bufflen = (int)strlen (charparg);
				}
				else
				{
					for (bufflen = 0; bufflen < precision && charparg[bufflen] != '\0'; ++bufflen)
					{ /* empty */ }
				}
			}
		}
		else if (type == '%')
		{ // Just print a '%': Output it with the next stage.
			base--;
			continue;
		}
		else if (type == 'n')
		{
			if (size == F_HALFHALF)
			{
				*va_arg (arglist, char *) = (char)inlen;
			}
			else if (size == F_HALF)
			{
				*va_arg (arglist, short *) = (short)inlen;
			}
			else if (size == F_LONG)
			{
				*va_arg (arglist, long *) = inlen;
			}
			else if (size == F_LONGLONG)
			{
				*va_arg (arglist, int64_t *) = inlen;
			}
			else if (size == F_BIGI)
			{
				*va_arg (arglist, ptrdiff_t *) = inlen;
			}
			else
			{
				*va_arg (arglist, int *) = inlen;
			}
		}
		else if (type == 'f' || type == 'F')
		{
			expchar = '\0';
			goto fp_begin;
		}
		else if (type == 'g' || type == 'G')
		{
			expchar = type - ('g' - 'e');
			if (precision == 0)
			{
				precision = 1;
			}
			goto fp_begin;
		}
		else if (type == 'H')
		{ // %H is an extension that behaves similarly to %g, except it automatically
		  // selects precision based on whatever will produce the smallest string.
			expchar = 'e';
			goto fp_begin;
		}
#if 0
		// The hdtoa function provided with FreeBSD uses a hexadecimal FP constant.
		// Microsoft's compiler does not support these, so I would need to hack it
		// together with ints instead. It's very do-able, but until I actually have
		// some reason to print hex FP numbers, I won't bother.
		else if (type == 'a' || type == 'A')
		{
			if (type == 'A')
			{
				xits = HEXits;
				hexprefix = 'X';
				expchar = 'P';
			}
			else
			{
				hexprefix = 'x';
				expchar = 'p';
			}
			if (precision >= 0)
			{
				precision++;
			}
			dblarg = va_arg(arglist, double);
			dtoaresult = obuff = hdtoa(dblarg, xits, precision, &expt, &signflag, &dtoaend);
			if (precision < 0)
			{
				precision = (int)(dtoaend - obuff);
			}
			if (expt == INT_MAX)
			{
				hexprefix = '\0';
			}
			goto fp_common;
		}
#endif
		else if (type == 'e' || type == 'E')
		{
			expchar = type;
			if (precision < 0)	// account for digit before decpt
			{
				precision = DEFPREC + 1;
			}
			else
			{
				precision++;
			}
fp_begin:
			if (precision < 0)
			{
				precision = DEFPREC;
			}
			dblarg = va_arg(arglist, double);
			obuff = dtoaresult = dtoa(dblarg, type != 'H' ? (expchar ? 2 : 3) : 0, precision, &expt, &signflag, &dtoaend);
//fp_common:
			decimal_point = localeconv()->decimal_point;
			flags |= F_SIGNED;
			if (signflag)
			{
				flags |= F_NEGATIVE;
			}
			if (expt == 9999)	// inf or nan
			{
				if (*obuff == 'N')
				{
					obuff = (type >= 'a') ? "nan" : "NAN";
					flags &= ~F_SIGNED;
				}
				else
				{
					obuff = (type >= 'a') ? "inf" : "INF";
				}
				bufflen = 3;
				flags &= ~F_ZERO;
			}
			else
			{
				flags |= F_FPT;
				ndig = (int)(dtoaend - obuff);
				if (type == 'g' || type == 'G')
				{
					if (expt > -4 && expt <= precision)
					{ // Make %[gG] smell like %[fF].
						expchar = '\0';
						if (flags & F_HASH)
						{
							precision -= expt;
						}
						else
						{
							precision = ndig - expt;
						}
						if (precision < 0)
						{
							precision = 0;
						}
					}
					else
					{ // Make %[gG] smell like %[eE], but trim trailing zeroes if no # flag.
						if (!(flags & F_HASH))
						{
							precision = ndig;
						}
					}
				}
				else if (type == 'H')
				{
					if (expt > -(ndig + 2) && expt <= (ndig + 4))
					{ // Make %H smell like %f
						expchar = '\0';
						precision = ndig - expt;
						if (precision < 0)
						{
							precision = 0;
						}
					}
					else
					{// Make %H smell like %e
						precision = ndig;
					}
				}
				if (expchar)
				{
					expsize = exponent(expstr, expt - 1, expchar);
					bufflen = expsize + precision;
					if (precision > 1 || (flags & F_HASH))
					{
						++bufflen;
					}
				}
				else
				{ // space for digits before decimal point
					if (expt > 0)
					{
						bufflen = expt;
					}
					else	// "0"
					{
						bufflen = 1;
					}
					// space for decimal pt and following digits
					if (precision != 0 || (flags & F_HASH))
					{
						bufflen += precision + 1;
					}
				}
			}
		}

		// Check for sign prefix (only for signed numbers)
		if (flags & F_SIGNED)
		{
			if (flags & F_NEGATIVE)
			{
				sign = '-';
			}
			else if (flags & F_PLUS)
			{
				sign = '+';
			}
			else if (flags & F_BLANK)
			{
				sign = ' ';
			}
		}

		// Construct complete prefix from sign and hex prefix character
		prefixlen = 0;
		if (sign != '\0')
		{
			prefix[0] = sign;
			prefixlen = 1;
		}
		if (hexprefix != '\0')
		{
			prefix[prefixlen] = '0';
			prefix[prefixlen + 1] = hexprefix;
			prefixlen += 2;
		}

		// Pad the output to the field width, if needed
		int fieldlen = prefixlen + postprefixzeros + bufflen;
		const char *pad = (flags & F_ZERO) ? zeroes : spaces;

		// If the output is right aligned and zero-padded, then the prefix must come before the padding.
		if ((flags & (F_ZERO|F_MINUS)) == F_ZERO && prefixlen > 0)
		{
			outlen += output (outputData, prefix, prefixlen);
			prefixlen = 0;
		}
        if (!(flags & F_MINUS) && fieldlen < width)
		{ // Field is right-justified, so padding comes first
			outlen += writepad (output, outputData, pad, sizeof(spaces), width - fieldlen);
			width = -1;
		}

		// Output field: Prefix, post-prefix zeros, buffer text
		if (prefixlen > 0)
		{
			outlen += output (outputData, prefix, prefixlen);
		}
		outlen += writepad (output, outputData, zeroes, sizeof(spaces), postprefixzeros);
		if (!(flags & F_FPT))
		{
			if (bufflen > 0)
			{
				outlen += output (outputData, obuff, bufflen);
			}
		}
		else
		{
			if (expchar == '\0')	// %[fF] or sufficiently short %[gG]
			{
				if (expt <= 0)
				{
					outlen += output (outputData, zeroes, 1);
					if (precision != 0 || (flags & F_HASH))
					{
						outlen += output (outputData, decimal_point, 1);
					}
					outlen += writepad (output, outputData, zeroes, sizeof(zeroes), -expt);
					// already handled initial 0's
					precision += expt;
				}
				else
				{
					outlen += printandpad (output, outputData, obuff, dtoaend, expt, zeroes, sizeof(zeroes));
					obuff += expt;
					if (precision || (flags & F_HASH))
					{
						outlen += output (outputData, decimal_point, 1);
					}
				}
				outlen += printandpad (output, outputData, obuff, dtoaend, precision, zeroes, sizeof(zeroes));
			}
			else						// %[eE] or sufficiently long %[gG]
			{
				if (precision > 1 || (flags & F_HASH))
				{
					buffer[0] = *obuff++;
					buffer[1] = *decimal_point;
					outlen += output (outputData, buffer, 2);
					outlen += output (outputData, obuff, ndig - 1);
					outlen += writepad (output, outputData, zeroes, sizeof(zeroes), precision - ndig);
				}
				else		// XeYY
				{
					outlen += output (outputData, obuff, 1);
				}
				outlen += output (outputData, expstr, expsize);
			}
		}

        if ((flags & F_MINUS) && fieldlen < width)
		{ // Field is left-justified, so padding comes last
			outlen += writepad (output, outputData, pad, sizeof(spaces), width - fieldlen);
		}
		len += outlen;
		if (dtoaresult != NULL)
		{
			freedtoa(dtoaresult);
			dtoaresult = NULL;
		}
	}
	}
	}

	static int writepad (OutputFunc output, void *outputData, const char *pad, int padsize, int spaceToFill)
	{
		int outlen = 0;
		while (spaceToFill > 0)
		{
			int count = spaceToFill > padsize ? padsize : spaceToFill;
			outlen += output (outputData, pad, count);
			spaceToFill -= count;
		}
		return outlen;
	}

	static int printandpad (OutputFunc output, void *outputData, const char *p, const char *ep, int len, const char *with, int padsize)
	{
		int outlen = 0;
		int n2 = (int)(ep - p);
		if (n2 > len)
		{
			n2 = len;
		}
		if (n2 > 0)
		{
			outlen = output (outputData, p, n2);	
		}
		return outlen + writepad (output, outputData, with, padsize, len - (n2 > 0 ? n2 : 0));
	}

	static int exponent (char *p0, int exp, int fmtch)
	{
		char *p, *t;
		char expbuf[MAXEXPDIG];

		p = p0;
		*p++ = fmtch;
		if (exp < 0)
		{
			exp = -exp;
			*p++ = '-';
		}
		else
		{
			*p++ = '+';
		}
		t = expbuf + MAXEXPDIG;
		if (exp > 9)
		{
			do
			{
				*--t = '0' + (exp % 10);
			}
			while ((exp /= 10) > 9);
			*--t = '0' + exp;
			for(; t < expbuf + MAXEXPDIG; *p++ = *t++)
			{ }
		}
		else
		{
			// Exponents for decimal floating point conversions
			// (%[eEgG]) must be at least two characters long,
			// whereas exponents for hexadecimal conversions can
			// be only one character long.
			if (fmtch == 'e' || fmtch == 'E')
			{
				*p++ = '0';
			}
			*p++ = '0' + exp;
		}
		return (int)(p - p0);
	}
};

//========================================================================//
// snprintf / vsnprintf imitations

#ifdef __GNUC__
#define GCCPRINTF(stri,firstargi)	__attribute__((format(printf,stri,firstargi)))
#define GCCFORMAT(stri)				__attribute__((format(printf,stri,0)))
#define GCCNOWARN					__attribute__((unused))
#else
#define GCCPRINTF(a,b)
#define GCCFORMAT(a)
#define GCCNOWARN
#endif

struct snprintf_state
{
	char *buffer;
	size_t maxlen;
	size_t curlen;
	int ideallen;
};

static int myvsnprintf_helper(void *data, const char *cstr, int cstr_len)
{
	snprintf_state *state = (snprintf_state *)data;

	if (INT_MAX - cstr_len < state->ideallen)
	{
		state->ideallen = INT_MAX;
	}
	else
	{
		state->ideallen += cstr_len;
	}
	if (state->curlen + cstr_len > state->maxlen)
	{
		cstr_len = (int)(state->maxlen - state->curlen);
	}
	if (cstr_len > 0)
	{
		memcpy(state->buffer + state->curlen, cstr, cstr_len);
		state->curlen += cstr_len;
	}
	return cstr_len;
}

extern "C"
{

// Unlike the MS CRT function snprintf, this one always writes a terminating
// null character to the buffer. It also returns the full length of the string
// that would have been output if the buffer had been large enough. In other
// words, it follows BSD/Linux rules and not MS rules.
int myvsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
{
	size_t originalcount = count;
	if (count != 0)
	{
		count--;
	}
	if (count > INT_MAX)
	{ // This is probably an error. Output nothing.
		originalcount = 0;
		count = 0;
	}
	snprintf_state state = { buffer, count, 0, 0 };
	StringFormat::VWorker(myvsnprintf_helper, &state, format, argptr);
	if (originalcount > 0)
	{
		buffer[state.curlen] = '\0';
	}
	return state.ideallen;
}

int mysnprintf(char *buffer, size_t count, const char *format, ...)
{
	va_list argptr;
	va_start(argptr, format);
	int len = myvsnprintf(buffer, count, format, argptr);
	va_end(argptr);
	return len;
}

}