0ad/source/lib/sysdep/os/win/wprintf.cpp
2011-05-04 12:10:17 +00:00

526 lines
12 KiB
C++

/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* implementation of sys_vswprintf.
*/
#include "precompiled.h"
#if ARCH_IA32
/*
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 MSVC7.1, though it's supported natively by MSVC8)
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)
*/
#ifndef _UNICODE
#define _UNICODE
#endif
#include <tchar.h>
#ifdef _UNICODE
# define tstring wstring
# define tstringstream wstringstream
#else
# define tstring string
# define tstringstream stringstream
#endif
#include <string>
#include <vector>
#include <stdio.h>
#include <sstream>
#include <stdarg.h>
#if MSC_VERSION < 1400
# define USE_I64_FORMAT 1
#else
# define USE_I64_FORMAT 0
#endif
enum
{
SPECFLAG_THOUSANDS = 1, // '
SPECFLAG_LEFTJUSTIFIED = 2, // -
SPECFLAG_SIGNED = 4, // +
SPECFLAG_SPACEPREFIX = 8, // <space>
SPECFLAG_ALTERNATE = 16, // #
SPECFLAG_ZEROPAD = 32 // 0
};
struct FormatChunk
{
virtual ~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::tstring t) : text(t) {}
std::tstring 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::tstring flags_to_string(char flags)
{
std::tstring s;
const char* c = "\'-+ #0";
for (int i=0; i<6; ++i)
if (flags & (1<<i))
s += c[i];
return s;
}
template<typename T>
std::tstring to_string(T n)
{
std::tstring s;
std::tstringstream 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;
}
int sys_vswprintf(TCHAR* buffer, size_t count, const TCHAR* format, va_list argptr)
{
// To help quickly detect incorrect 'count' values, fill the buffer with 0s
memset(buffer, 0, count*sizeof(TCHAR));
/*
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<FormatChunk*> specs;
std::tstring 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 = (char)(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'))
{
DEBUG_WARN_ERR(ERR::LOGIC); // 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 = (char)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::tstring newformat;
std::vector<int> varsizes; // stores the size of each variable type, to allow stack twiddling
typedef std::vector<FormatChunk*>::iterator ChunkIt;
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
{
if ((*it)->ChunkType() == 0)
{
FormatVariable* s = static_cast<FormatVariable*>(*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 += _T("%");
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)
#if USE_I64_FORMAT
newformat += "I64";
#else
newformat += _T("ll");
#endif
else if (s->length == 0x00006868)
newformat += _T("hh");
}
else
{
newformat += (char) s->length;
}
}
newformat += s->type;
}
else
{
FormatString* s = static_cast<FormatString*>(*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:
#if !ARCH_IA32
#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<char*, char*> > stackitems;
va_list arglist = argptr;
//va_start(arglist, format);
const u8* newstackptr;
if (varsizes.size())
{
for (size_t i = 1; i < varsizes.size(); ++i)
{
if (varsizes[i] <= 0)
{
DEBUG_WARN_ERR(ERR::LOGIC); // 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<char*, char*>( arglist, arglist+size ));
arglist += size;
}
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
{
FormatChunk* chunk = *it;
if (chunk->ChunkType() == 0)
{
FormatVariable* s = static_cast<FormatVariable*>(chunk);
if (s->position <= 0)
{
DEBUG_WARN_ERR(ERR::LOGIC); // 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 = (const u8*)newstack.c_str();
}
else
{
newstackptr = (const u8*)arglist;
}
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
if ((*it)->ChunkType() == 0)
delete static_cast<FormatVariable*>(*it);
else
delete static_cast<FormatString*>(*it);
int ret = _vsntprintf(buffer, count, newformat.c_str(), (va_list)newstackptr);
// For consistency with GCC's vsnprintf, make sure the buffer is null-terminated
// and return an error if that truncates the output
if(count > 0)
buffer[count-1] = '\0';
if (ret == (int)count)
return -1;
return ret;
}
#endif