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)
This commit is contained in:
Randy Heit 2008-07-23 04:57:26 +00:00
commit fb50df2c63
463 changed files with 13488 additions and 4920 deletions

View file

@ -1,11 +1,96 @@
/*
** 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>
@ -14,21 +99,32 @@ typedef unsigned __int64 uint64_t;
typedef signed __int64 int64_t;
#endif
// 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.
/*
* 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;
@ -40,43 +136,6 @@ namespace StringFormat
return len;
}
static inline 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;
}
// Gasp! This is supposed to be a replacement for sprintf formatting, but
// I used sprintf for doubles anyway! Oh no!
static int fmt_fp (OutputFunc output, void *outputData, int flags, int precision, int width, double number, char type)
{
char *buff;
char format[16];
int i;
format[0] = '%';
i = 1;
if (flags & F_MINUS) format[i++] = '-';
if (flags & F_PLUS) format[i++] = '+';
if (flags & F_ZERO) format[i++] = '0';
if (flags & F_BLANK) format[i++] = ' ';
if (flags & F_HASH) format[i++] = '#';
format[i++] = '*';
format[i++] = '.';
format[i++] = '*';
format[i++] = type;
format[i++] = '\0';
buff = (char *)alloca (1000 + precision);
i = sprintf (buff, format, width, precision, number);
return output (outputData, buff, i);
}
int VWorker (OutputFunc output, void *outputData, const char *fmt, va_list arglist)
{
const char *c;
@ -233,18 +292,13 @@ namespace StringFormat
// Now that that's all out of the way, we should be pointing at the type specifier
{
static const char hexits[18] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','0','x'};
static const char HEXits[18] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','0','X'};
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 plusprefix = '+';
static const char minusprefix = '-';
static const char dotchar = '.';
const char *prefix = NULL;
int prefixlen = 0;
char prefix[3];
int prefixlen;
char hexprefix = '\0';
char sign = '\0';
int postprefixzeros = 0;
int size = flags & 0xF000;
char buffer[32], *ibuff;
char buffer[80], *ibuff;
const char *obuff = 0;
char type = *c++;
int bufflen = 0;
@ -253,8 +307,32 @@ namespace StringFormat
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
@ -263,7 +341,8 @@ namespace StringFormat
if (type == 'x' || type == 'X' ||
type == 'p' ||
type == 'd' || type == 'u' || type == 'i' ||
type == 'o')
type == 'o' ||
type == 'B')
{
if (type == 'X' || type == 'p')
{
@ -360,6 +439,14 @@ namespace StringFormat
}
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')
@ -413,10 +500,19 @@ namespace StringFormat
}
else if (type == 'u')
{ // Decimal
while (intarg != 0)
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
@ -425,6 +521,13 @@ namespace StringFormat
*--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)
@ -439,14 +542,16 @@ namespace StringFormat
{
if (bufflen >= precision)
{
prefix = zeroes;
prefixlen = 1;
sign = '0';
}
}
else if (type == 'x' || type == 'X')
{
prefix = xits + 16;
prefixlen = 2;
hexprefix = type;
}
else if (type == 'B')
{
hexprefix = '!';
}
}
bufflen = (int)(ptrdiff_t)(&buffer[sizeof(buffer)] - ibuff);
@ -519,59 +624,149 @@ namespace StringFormat
*va_arg (arglist, int *) = inlen;
}
}
else if (type == 'e' || type == 'E' ||
type == 'f' || type == 'F' ||
type == 'g' || type == 'G')
else if (type == 'f' || type == 'F')
{
// IEEE 754 floating point numbers
#ifdef _MSC_VER
#define FP_SIGN_MASK (1ui64<<63)
#define FP_EXPONENT_MASK (2047ui64<<52)
#define FP_FRACTION_MASK ((1ui64<<52)-1)
#else
#define FP_SIGN_MASK (1llu<<63)
#define FP_EXPONENT_MASK (2047llu<<52)
#define FP_FRACTION_MASK ((1llu<<52)-1)
#endif
union
expchar = '\0';
goto fp_begin;
}
else if (type == 'g' || type == 'G')
{
expchar = type - ('g' - 'e');
if (precision == 0)
{
double d;
uint64_t i64;
} number;
number.d = va_arg (arglist, double);
if (precision < 0) precision = 6;
flags |= F_SIGNED;
if ((number.i64 & FP_EXPONENT_MASK) == FP_EXPONENT_MASK)
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')
{
if (number.i64 & FP_SIGN_MASK)
{
flags |= F_NEGATIVE;
}
if ((number.i64 & FP_FRACTION_MASK) == 0)
{
obuff = "Infinity";
bufflen = 8;
}
else if ((number.i64 & ((FP_FRACTION_MASK+1)>>1)) == 0)
{
obuff = "NaN";
bufflen = 3;
}
else
{
obuff = "Ind";
}
xits = HEXits;
hexprefix = 'X';
expchar = 'P';
}
else
{
// Converting a binary floating point number to an ASCII decimal
// representation is non-trivial, so I'm not going to do it myself.
// (At least for now.)
len += fmt_fp (output, outputData, flags, precision, width, number.d, type);
continue;
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;
}
}
}
}
@ -580,21 +775,32 @@ namespace StringFormat
{
if (flags & F_NEGATIVE)
{
prefix = &minusprefix;
prefixlen = 1;
sign = '-';
}
else if (flags & F_PLUS)
{
prefix = &plusprefix;
prefixlen = 1;
sign = '+';
}
else if (flags & F_BLANK)
{
prefix = spaces;
prefixlen = 1;
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;
@ -617,9 +823,55 @@ namespace StringFormat
outlen += output (outputData, prefix, prefixlen);
}
outlen += writepad (output, outputData, zeroes, sizeof(spaces), postprefixzeros);
if (bufflen > 0)
if (!(flags & F_FPT))
{
outlen += output (outputData, obuff, bufflen);
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)
@ -627,7 +879,166 @@ namespace StringFormat
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;
}
}