#include "precompiled.h" /* See http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html for the specification (apparently an extension to ISO C) that was used when creating this code. */ /* Added features (compared to MSVC's printf): Positional parameters (e.g. "%1$d", where '1' means '1st in the parameter list') %lld (equivalent to %I64d in MSVC) Unsupported features (compared to a perfect implementation): ' <-- because MSVC doesn't support it %S } %C } because they're unnecessary and can cause confusion * <-- probably works in some situations, but don't expect it to *m$ <-- positional precision parameters, because they're not worthwhile portability <-- just use GCC efficiency <-- nothing in the spec says it should be as fast as possible (the code could be made a lot faster if speed mattered more than non-fixed size buffers) */ #include #include #include #include #include #include #include enum { SPECFLAG_THOUSANDS = 1, // ' SPECFLAG_LEFTJUSTIFIED = 2, // - SPECFLAG_SIGNED = 4, // + SPECFLAG_SPACEPREFIX = 8, // SPECFLAG_ALTERNATE = 16, // # SPECFLAG_ZEROPAD = 32, // 0 }; struct FormatChunk { virtual int ChunkType() = 0; // 0 = FormatSpecification, 1 = FormatString }; struct FormatVariable : public FormatChunk { int ChunkType() { return 0; } int position; // undefined if the format includes no positional elements char flags; // ['-+ #0] int width; // -1 for *, 0 for unspecified int precision; // -1 for * int length; // "\0\0\0l", "\0\0hh", etc char type; // 'd', etc }; struct FormatString : public FormatChunk { int ChunkType() { return 1; } FormatString(std::string t) : text(t) {} std::string text; }; int get_flag(TCHAR c) { switch (c) { case _T('\''): return SPECFLAG_THOUSANDS; case _T('-'): return SPECFLAG_LEFTJUSTIFIED; case _T('+'): return SPECFLAG_SIGNED; case _T(' '): return SPECFLAG_SPACEPREFIX; case _T('#'): return SPECFLAG_ALTERNATE; case _T('0'): return SPECFLAG_ZEROPAD; } return 0; } std::string flags_to_string(char flags) { std::string s; const char* c = "\'-+ #0"; for (int i=0; i<6; ++i) if (flags & (1< std::string to_string(T n) { std::string s; std::stringstream str; str << n; str >> s; return s; } int is_lengthmod(TCHAR c) { return c == _T('h') || c == _T('l') || c == _T('j') || c == _T('z') || c == _T('t') || c == _T('L'); } // _T2('l') == 'll' #define _T2(a) ((a<<8) | a) int type_size(TCHAR type, int length) { switch (type) { case 'd': case 'i': switch (length) { case _T ('l'): return sizeof(long); case _T2('l'): return sizeof(long long); case _T ('h'): return sizeof(short); case _T2('h'): return sizeof(char); default: return sizeof(int); } case 'o': case 'u': case 'x': case 'X': return sizeof(unsigned); case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': if (length == _T('L')) return sizeof(long double); else return sizeof(double); case 'c': // "%lc" is a wide character, passed as a wint_t (ushort) if (length == _T('l')) return sizeof(wint_t); // "%c" is an int, apparently else return sizeof(int); case 's': if (length == _T('l')) return sizeof(wchar_t*); else return sizeof(char*); case 'p': return sizeof(void*); case 'n': return sizeof(int*); } return 0; } extern "C" int vsnprintf2(TCHAR* buffer, size_t count, const TCHAR* format, va_list argptr) { /* Format 'variable' specifications are (in pseudo-Perl regexp syntax): % (\d+$)? ['-+ #0]? (* | \d+)? (. (* | \d*) )? (hh|h|l|ll|j|z|t|L)? [diouxXfFeEgGaAcspnCS%] position flags width precision length type */ /**** Parse the format string into constant/variable chunks ****/ std::vector specs; std::string stringchunk; TCHAR chr; #define readchar(x) if ((x = *format++) == '\0') { delete s; goto finished_reading; } while ((chr = *format++) != '\0') { if (chr == _T('%')) { // Handle %% correctly if (*format == _T('%')) { stringchunk += _T('%'); continue; } // End the current string and start a new spec chunk if (stringchunk.length()) { specs.push_back(new FormatString(stringchunk)); stringchunk = _T(""); } FormatVariable *s = new FormatVariable; s->position = -1; s->flags = 0; s->width = 0; s->precision = 0; s->length = 0; s->type = 0; // Read either the position or the width int number = 0; while (1) { readchar(chr); // Read flags (but not if it's a 0 appearing after other digits) if (!number && get_flag(chr)) s->flags |= get_flag(chr); // Read decimal numbers (position or width) else if (isdigit(chr)) number = number*10 + (chr-'0'); // If we've reached a $, 'number' was the position, // so remember it and start getting the width else if (chr == _T('$')) { s->position = number; number = 0; } // End of the number section else { // Remember the width s->width = number; // Start looking for a precision if (chr == _T('.')) { // Found a precision: read the digits number = 0; while (1) { readchar(chr); if (isdigit(chr)) number = number*10 + (chr-'0'); else { s->precision = number; break; } } } // Finished dealing with any precision. // Now check for length and type codes. if (chr == _T('I')) { assert(! "MSVC-style \"%I64\" is not allowed!"); } if (is_lengthmod(chr)) { s->length = chr; // Check for ll and hh if (chr == _T('l') || chr == _T('h')) { if (*format == chr) { s->length |= (chr << 8); ++format; } } readchar(chr); } s->type = chr; specs.push_back(s); break; } } } else { stringchunk += chr; } } #undef readchar finished_reading: if (stringchunk.length()) { specs.push_back(new FormatString(stringchunk)); stringchunk = _T(""); } /**** Build a new format string (to pass to the system printf) ****/ std::string newformat; std::vector varsizes; // stores the size of each variable type, to allow stack twiddling typedef std::vector::iterator ChunkIt; for (ChunkIt it = specs.begin(); it != specs.end(); ++it) { if ((*it)->ChunkType() == 0) { FormatVariable* s = static_cast(*it); if (s->position > 0) { // Grow if necessary if (s->position >= (int)varsizes.size()) varsizes.resize(s->position+1, -1); // Store the size of the current type varsizes[s->position] = type_size(s->type, s->length); } newformat += "%"; if (s->flags) newformat += flags_to_string(s->flags); if (s->width == -1) newformat += '*'; else if (s->width) newformat += to_string(s->width); if (s->precision) { newformat += '.'; if (s->precision == -1) newformat += '*'; else newformat += to_string(s->precision); } if (s->length) { if (s->length > 256) { if (s->length == 0x00006c6c) #ifdef _MSC_VER newformat += "I64"; // MSVC compatibility #else newformat += "ll"; #endif else if (s->length == 0x00006868) newformat += "hh"; } else { newformat += (char) s->length; } } newformat += s->type; } else { FormatString* s = static_cast(*it); newformat += s->text; } } /* varargs on x86: All types are padded to 4 bytes, so size-in-stack == _INTSIZEOF(type). No special alignment is required. first+_INTSIZEOF(first) == first item in stack Keep adding _INTSIZEOF(item) to get the next item */ // Because of those dangerous assumptions about varargs: #ifndef _M_IX86 #error SLIGHTLY FATAL ERROR: Only x86 is supported! #endif // Highly efficient buffer to store the rearranged copy of the stack std::string newstack; std::vector< std::pair > stackitems; va_list arglist = argptr; //va_start(arglist, format); const char* newstackptr; if (varsizes.size()) { for (size_t i = 1; i < varsizes.size(); ++i) { if (varsizes[i] <= 0) { assert(! "Invalid variable type somewhere - make sure all variable things are positional and defined"); return -1; } // Based on _INTSIZEOF in stdarg.h: // (Warning - slightly non-portable. But we use gcc's default printf // when portability matters.) #define INTSIZE(n) ( (n + sizeof(int) - 1) & ~(sizeof(int) - 1) ) size_t size = INTSIZE(varsizes[i]); stackitems.push_back( std::pair( arglist, arglist+size )); arglist += size; } for (ChunkIt it = specs.begin(); it != specs.end(); ++it) { if ((*it)->ChunkType() == 0) { FormatVariable* s = static_cast(*it); if (s->position <= 0) { assert(! "Invalid use of positional elements - make sure all variable things are positional and defined"); return -1; } newstack += std::string( stackitems[s->position-1].first, stackitems[s->position-1].second ); } } newstackptr = newstack.c_str(); } else { newstackptr = arglist; } for (ChunkIt it = specs.begin(); it != specs.end(); ++it) if ((*it)->ChunkType() == 0) delete static_cast(*it); else delete static_cast(*it); int ret = _vsnprintf(buffer, count, newformat.c_str(), (va_list)newstackptr); return ret; }