gzdoom/src/zstrformat.cpp
Randy Heit fb50df2c63 About a week's worth of changes here. As a heads-up, I wouldn't be
surprised if this doesn't build in Linux right now. The CMakeLists.txt
were checked with MinGW and NMake, but how they fair under Linux is an
unknown to me at this time.

- Converted most sprintf (and all wsprintf) calls to either mysnprintf or
  FStrings, depending on the situation.
- Changed the strings in the wbstartstruct to be FStrings.
- Changed myvsnprintf() to output nothing if count is greater than INT_MAX.
  This is so that I can use a series of mysnprintf() calls and advance the
  pointer for each one. Once the pointer goes beyond the end of the buffer,
  the count will go negative, but since it's an unsigned type it will be
  seen as excessively huge instead. This should not be a problem, as there's
  no reason for ZDoom to be using text buffers larger than 2 GB anywhere.
- Ripped out the disabled bit from FGameConfigFile::MigrateOldConfig().
- Changed CalcMapName() to return an FString instead of a pointer to a static
  buffer.
- Changed startmap in d_main.cpp into an FString.
- Changed CheckWarpTransMap() to take an FString& as the first argument.
- Changed d_mapname in g_level.cpp into an FString.
- Changed DoSubstitution() in ct_chat.cpp to place the substitutions in an
  FString.
- Fixed: The MAPINFO parser wrote into the string buffer to construct a map
  name when given a Hexen map number. This was fine with the old scanner
  code, but only a happy coincidence prevents it from crashing with the new
  code
- Added the 'B' conversion specifier to StringFormat::VWorker() for printing
  binary numbers.
- Added CMake support for building with MinGW, MSYS, and NMake. Linux support
  is probably broken until I get around to booting into Linux again. Niceties
  provided over the existing Makefiles they're replacing:
  * All command-line builds can use the same build system, rather than having
    a separate one for MinGW and another for Linux.
  * Microsoft's NMake tool is supported as a target.
  * Progress meters.
  * Parallel makes work from a fresh checkout without needing to be primed
    first with a single-threaded make.
  * Porting to other architectures should be simplified, whenever that day
    comes.
- Replaced the makewad tool with zipdir. This handles the dependency tracking
  itself instead of generating an external makefile to do it, since I couldn't
  figure out how to generate a makefile with an external tool and include it
  with a CMake-generated makefile. Where makewad used a master list of files
  to generate the package file, zipdir just zips the entire contents of one or
  more directories.
- Added the gdtoa package from netlib's fp library so that ZDoom's printf-style
  formatting can be entirely independant of the CRT.

SVN r1082 (trunk)
2008-07-23 04:57:26 +00:00

1044 lines
26 KiB
C++

/*
** 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 <math.h>
#include <malloc.h>
#include <locale.h>
#include "zstring.h"
#include "gdtoa.h"
#ifndef _MSC_VER
#include <stdint.h>
#else
typedef unsigned __int64 uint64_t;
typedef signed __int64 int64_t;
#endif
/*
* 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','.'};
static const char dotchar = '.';
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);
buffer[0] = char(intarg);
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;
}
#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, expchar ? 2 : 3, precision, &expt, &signflag, &dtoaend);
//fp_common:
decimal_point = localeconv()->decimal_point;
flags |= F_SIGNED;
if (signflag)
{
flags |= F_NEGATIVE;
}
if (expt == INT_MAX) // 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;
}
}
}
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;
}
}