From 473464069216bb835735a6c164ee769679a0ab03 Mon Sep 17 00:00:00 2001 From: =?utf8?q?V=C3=A1clav=20Slav=C3=ADk?= Date: Mon, 20 Aug 2007 19:20:10 +0000 Subject: [PATCH] fixed variadic templates in the case when char value is passed in place of (e.g.) %i or %d argument git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@48224 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/private/wxprintf.h | 856 ++++++++++++++++++++++++++++++++++ include/wx/strvararg.h | 225 ++++++--- src/common/strvararg.cpp | 71 ++- src/common/wxprintf.cpp | 828 +------------------------------- tests/strings/vararg.cpp | 29 ++ 5 files changed, 1124 insertions(+), 885 deletions(-) create mode 100644 include/wx/private/wxprintf.h diff --git a/include/wx/private/wxprintf.h b/include/wx/private/wxprintf.h new file mode 100644 index 0000000000..f797981812 --- /dev/null +++ b/include/wx/private/wxprintf.h @@ -0,0 +1,856 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wx/private/wxprintf.h +// Purpose: wxWidgets wxPrintf() implementation +// Author: Ove Kaven +// Modified by: Ron Lee, Francesco Montorsi +// Created: 09/04/99 +// RCS-ID: $Id$ +// Copyright: (c) wxWidgets copyright +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_WXPRINTF_H_ +#define _WX_PRIVATE_WXPRINTF_H_ + +// --------------------------------------------------------------------------- +// headers and macros +// --------------------------------------------------------------------------- + +#include "wx/crt.h" + +#include + +#if defined(__MWERKS__) && __MSL__ >= 0x6000 +namespace std {} +using namespace std ; +#endif + +// prefer snprintf over sprintf +#if defined(__VISUALC__) || \ + (defined(__BORLANDC__) && __BORLANDC__ >= 0x540) + #define system_sprintf(buff, max, flags, data) \ + ::_snprintf(buff, max, flags, data) +#elif defined(HAVE_SNPRINTF) + #define system_sprintf(buff, max, flags, data) \ + ::snprintf(buff, max, flags, data) +#else // NB: at least sprintf() should always be available + // since 'max' is not used in this case, wxVsnprintf() should always + // ensure that 'buff' is big enough for all common needs + // (see wxMAX_SVNPRINTF_FLAGBUFFER_LEN and wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN) + #define system_sprintf(buff, max, flags, data) \ + ::sprintf(buff, flags, data) + + #define SYSTEM_SPRINTF_IS_UNSAFE +#endif + +// --------------------------------------------------------------------------- +// printf format string parsing +// --------------------------------------------------------------------------- + +// some limits of our implementation +#define wxMAX_SVNPRINTF_ARGUMENTS 64 +#define wxMAX_SVNPRINTF_FLAGBUFFER_LEN 32 +#define wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN 512 + + +// the conversion specifiers accepted by wxCRT_VsnprintfW +enum wxPrintfArgType { + wxPAT_INVALID = -1, + + wxPAT_INT, // %d, %i, %o, %u, %x, %X + wxPAT_LONGINT, // %ld, etc +#ifdef wxLongLong_t + wxPAT_LONGLONGINT, // %Ld, etc +#endif + wxPAT_SIZET, // %Zd, etc + + wxPAT_DOUBLE, // %e, %E, %f, %g, %G + wxPAT_LONGDOUBLE, // %le, etc + + wxPAT_POINTER, // %p + + wxPAT_CHAR, // %hc (in ANSI mode: %c, too) + wxPAT_WCHAR, // %lc (in Unicode mode: %c, too) + + wxPAT_PCHAR, // %s (related to a char *) + wxPAT_PWCHAR, // %s (related to a wchar_t *) + + wxPAT_NINT, // %n + wxPAT_NSHORTINT, // %hn + wxPAT_NLONGINT // %ln +}; + +// an argument passed to wxCRT_VsnprintfW +typedef union { + int pad_int; // %d, %i, %o, %u, %x, %X + long int pad_longint; // %ld, etc +#ifdef wxLongLong_t + wxLongLong_t pad_longlongint; // %Ld, etc +#endif + size_t pad_sizet; // %Zd, etc + + double pad_double; // %e, %E, %f, %g, %G + long double pad_longdouble; // %le, etc + + void *pad_pointer; // %p + + char pad_char; // %hc (in ANSI mode: %c, too) + wchar_t pad_wchar; // %lc (in Unicode mode: %c, too) + + void *pad_str; // %s + + int *pad_nint; // %n + short int *pad_nshortint; // %hn + long int *pad_nlongint; // %ln +} wxPrintfArg; + +// helper for converting string into either char* or wchar_t* dependening +// on the type of wxPrintfConvSpec instantiation: +template struct wxPrintfStringHelper {}; + +template<> struct wxPrintfStringHelper +{ + typedef const wxWX2MBbuf ConvertedType; + static ConvertedType Convert(const wxString& s) { return s.mb_str(); } +}; + +template<> struct wxPrintfStringHelper +{ + typedef const wxWX2WCbuf ConvertedType; + static ConvertedType Convert(const wxString& s) { return s.wc_str(); } +}; + + +// Contains parsed data relative to a conversion specifier given to +// wxCRT_VsnprintfW and parsed from the format string +// NOTE: in C++ there is almost no difference between struct & classes thus +// there is no performance gain by using a struct here... +template +class wxPrintfConvSpec +{ +public: + + // the position of the argument relative to this conversion specifier + size_t m_pos; + + // the type of this conversion specifier + wxPrintfArgType m_type; + + // the minimum and maximum width + // when one of this var is set to -1 it means: use the following argument + // in the stack as minimum/maximum width for this conversion specifier + int m_nMinWidth, m_nMaxWidth; + + // does the argument need to the be aligned to left ? + bool m_bAlignLeft; + + // pointer to the '%' of this conversion specifier in the format string + // NOTE: this points somewhere in the string given to the Parse() function - + // it's task of the caller ensure that memory is still valid ! + const CharType *m_pArgPos; + + // pointer to the last character of this conversion specifier in the + // format string + // NOTE: this points somewhere in the string given to the Parse() function - + // it's task of the caller ensure that memory is still valid ! + const CharType *m_pArgEnd; + + // a little buffer where formatting flags like #+\.hlqLZ are stored by Parse() + // for use in Process() + // NB: even if this buffer is used only for numeric conversion specifiers + // and thus could be safely declared as a char[] buffer, we want it to + // be wchar_t so that in Unicode builds we can avoid to convert its + // contents to Unicode chars when copying it in user's buffer. + char m_szFlags[wxMAX_SVNPRINTF_FLAGBUFFER_LEN]; + + +public: + + // we don't declare this as a constructor otherwise it would be called + // automatically and we don't want this: to be optimized, wxCRT_VsnprintfW + // calls this function only on really-used instances of this class. + void Init(); + + // Parses the first conversion specifier in the given string, which must + // begin with a '%'. Returns false if the first '%' does not introduce a + // (valid) conversion specifier and thus should be ignored. + bool Parse(const CharType *format); + + // Process this conversion specifier and puts the result in the given + // buffer. Returns the number of characters written in 'buf' or -1 if + // there's not enough space. + int Process(CharType *buf, size_t lenMax, wxPrintfArg *p, size_t written); + + // Loads the argument of this conversion specifier from given va_list. + bool LoadArg(wxPrintfArg *p, va_list &argptr); + +private: + // An helper function of LoadArg() which is used to handle the '*' flag + void ReplaceAsteriskWith(int w); +}; + +template +void wxPrintfConvSpec::Init() +{ + m_nMinWidth = 0; + m_nMaxWidth = 0xFFFF; + m_pos = 0; + m_bAlignLeft = false; + m_pArgPos = m_pArgEnd = NULL; + m_type = wxPAT_INVALID; + + // this character will never be removed from m_szFlags array and + // is important when calling sprintf() in wxPrintfConvSpec::Process() ! + m_szFlags[0] = '%'; +} + +template +bool wxPrintfConvSpec::Parse(const CharType *format) +{ + bool done = false; + + // temporary parse data + size_t flagofs = 1; + bool in_prec, // true if we found the dot in some previous iteration + prec_dot; // true if the dot has been already added to m_szFlags + int ilen = 0; + + m_bAlignLeft = in_prec = prec_dot = false; + m_pArgPos = m_pArgEnd = format; + do + { +#define CHECK_PREC \ + if (in_prec && !prec_dot) \ + { \ + m_szFlags[flagofs++] = '.'; \ + prec_dot = true; \ + } + + // what follows '%'? + const CharType ch = *(++m_pArgEnd); + switch ( ch ) + { + case wxT('\0'): + return false; // not really an argument + + case wxT('%'): + return false; // not really an argument + + case wxT('#'): + case wxT('0'): + case wxT(' '): + case wxT('+'): + case wxT('\''): + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('-'): + CHECK_PREC + m_bAlignLeft = true; + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('.'): + CHECK_PREC + in_prec = true; + prec_dot = false; + m_nMaxWidth = 0; + // dot will be auto-added to m_szFlags if non-negative + // number follows + break; + + case wxT('h'): + ilen = -1; + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('l'): + // NB: it's safe to use flagofs-1 as flagofs always start from 1 + if (m_szFlags[flagofs-1] == 'l') // 'll' modifier is the same as 'L' or 'q' + ilen = 2; + else + ilen = 1; + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('q'): + case wxT('L'): + ilen = 2; + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + break; +#ifdef __WXMSW__ + // under Windows we support the special '%I64' notation as longlong + // integer conversion specifier for MSVC compatibility + // (it behaves exactly as '%lli' or '%Li' or '%qi') + case wxT('I'): + if (*(m_pArgEnd+1) != wxT('6') || + *(m_pArgEnd+2) != wxT('4')) + return false; // bad format + + m_pArgEnd++; + m_pArgEnd++; + + ilen = 2; + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + m_szFlags[flagofs++] = '6'; + m_szFlags[flagofs++] = '4'; + break; +#endif // __WXMSW__ + + case wxT('Z'): + ilen = 3; + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('*'): + if (in_prec) + { + CHECK_PREC + + // tell Process() to use the next argument + // in the stack as maxwidth... + m_nMaxWidth = -1; + } + else + { + // tell Process() to use the next argument + // in the stack as minwidth... + m_nMinWidth = -1; + } + + // save the * in our formatting buffer... + // will be replaced later by Process() + m_szFlags[flagofs++] = char(ch); + break; + + case wxT('1'): case wxT('2'): case wxT('3'): + case wxT('4'): case wxT('5'): case wxT('6'): + case wxT('7'): case wxT('8'): case wxT('9'): + { + int len = 0; + CHECK_PREC + while ( (*m_pArgEnd >= CharType('0')) && + (*m_pArgEnd <= CharType('9')) ) + { + m_szFlags[flagofs++] = char(*m_pArgEnd); + len = len*10 + (*m_pArgEnd - wxT('0')); + m_pArgEnd++; + } + + if (in_prec) + m_nMaxWidth = len; + else + m_nMinWidth = len; + + m_pArgEnd--; // the main loop pre-increments n again + } + break; + + case wxT('$'): // a positional parameter (e.g. %2$s) ? + { + if (m_nMinWidth <= 0) + break; // ignore this formatting flag as no + // numbers are preceding it + + // remove from m_szFlags all digits previously added + do { + flagofs--; + } while (m_szFlags[flagofs] >= '1' && + m_szFlags[flagofs] <= '9'); + + // re-adjust the offset making it point to the + // next free char of m_szFlags + flagofs++; + + m_pos = m_nMinWidth; + m_nMinWidth = 0; + } + break; + + case wxT('d'): + case wxT('i'): + case wxT('o'): + case wxT('u'): + case wxT('x'): + case wxT('X'): + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + m_szFlags[flagofs] = '\0'; + if (ilen == 0) + m_type = wxPAT_INT; + else if (ilen == -1) + // NB: 'short int' value passed through '...' + // is promoted to 'int', so we have to get + // an int from stack even if we need a short + m_type = wxPAT_INT; + else if (ilen == 1) + m_type = wxPAT_LONGINT; + else if (ilen == 2) +#ifdef wxLongLong_t + m_type = wxPAT_LONGLONGINT; +#else // !wxLongLong_t + m_type = wxPAT_LONGINT; +#endif // wxLongLong_t/!wxLongLong_t + else if (ilen == 3) + m_type = wxPAT_SIZET; + done = true; + break; + + case wxT('e'): + case wxT('E'): + case wxT('f'): + case wxT('g'): + case wxT('G'): + CHECK_PREC + m_szFlags[flagofs++] = char(ch); + m_szFlags[flagofs] = '\0'; + if (ilen == 2) + m_type = wxPAT_LONGDOUBLE; + else + m_type = wxPAT_DOUBLE; + done = true; + break; + + case wxT('p'): + m_type = wxPAT_POINTER; + m_szFlags[flagofs++] = char(ch); + m_szFlags[flagofs] = '\0'; + done = true; + break; + + case wxT('c'): + if (ilen == -1) + { + // in Unicode mode %hc == ANSI character + // and in ANSI mode, %hc == %c == ANSI... + m_type = wxPAT_CHAR; + } + else if (ilen == 1) + { + // in ANSI mode %lc == Unicode character + // and in Unicode mode, %lc == %c == Unicode... + m_type = wxPAT_WCHAR; + } + else + { +#if wxUSE_UNICODE + // in Unicode mode, %c == Unicode character + m_type = wxPAT_WCHAR; +#else + // in ANSI mode, %c == ANSI character + m_type = wxPAT_CHAR; +#endif + } + done = true; + break; + + case wxT('s'): + if (ilen == -1) + { + // Unicode mode wx extension: we'll let %hs mean non-Unicode + // strings (when in ANSI mode, %s == %hs == ANSI string) + m_type = wxPAT_PCHAR; + } + else if (ilen == 1) + { + // in Unicode mode, %ls == %s == Unicode string + // in ANSI mode, %ls == Unicode string + m_type = wxPAT_PWCHAR; + } + else + { +#if wxUSE_UNICODE + m_type = wxPAT_PWCHAR; +#else + m_type = wxPAT_PCHAR; +#endif + } + done = true; + break; + + case wxT('n'): + if (ilen == 0) + m_type = wxPAT_NINT; + else if (ilen == -1) + m_type = wxPAT_NSHORTINT; + else if (ilen >= 1) + m_type = wxPAT_NLONGINT; + done = true; + break; + + default: + // bad format, don't consider this an argument; + // leave it unchanged + return false; + } + + if (flagofs == wxMAX_SVNPRINTF_FLAGBUFFER_LEN) + { + wxLogDebug(wxT("Too many flags specified for a single conversion specifier!")); + return false; + } + } + while (!done); + + return true; // parsing was successful +} + +template +void wxPrintfConvSpec::ReplaceAsteriskWith(int width) +{ + char temp[wxMAX_SVNPRINTF_FLAGBUFFER_LEN]; + + // find the first * in our flag buffer + char *pwidth = strchr(m_szFlags, '*'); + wxCHECK_RET(pwidth, _T("field width must be specified")); + + // save what follows the * (the +1 is to skip the asterisk itself!) + strcpy(temp, pwidth+1); + if (width < 0) + { + pwidth[0] = wxT('-'); + pwidth++; + } + + // replace * with the actual integer given as width +#ifndef SYSTEM_SPRINTF_IS_UNSAFE + int maxlen = (m_szFlags + wxMAX_SVNPRINTF_FLAGBUFFER_LEN - pwidth) / + sizeof(*m_szFlags); +#endif + int offset = system_sprintf(pwidth, maxlen, "%d", abs(width)); + + // restore after the expanded * what was following it + strcpy(pwidth+offset, temp); +} + +template +bool wxPrintfConvSpec::LoadArg(wxPrintfArg *p, va_list &argptr) +{ + // did the '*' width/precision specifier was used ? + if (m_nMaxWidth == -1) + { + // take the maxwidth specifier from the stack + m_nMaxWidth = va_arg(argptr, int); + if (m_nMaxWidth < 0) + m_nMaxWidth = 0; + else + ReplaceAsteriskWith(m_nMaxWidth); + } + + if (m_nMinWidth == -1) + { + // take the minwidth specifier from the stack + m_nMinWidth = va_arg(argptr, int); + + ReplaceAsteriskWith(m_nMinWidth); + if (m_nMinWidth < 0) + { + m_bAlignLeft = !m_bAlignLeft; + m_nMinWidth = -m_nMinWidth; + } + } + + switch (m_type) { + case wxPAT_INT: + p->pad_int = va_arg(argptr, int); + break; + case wxPAT_LONGINT: + p->pad_longint = va_arg(argptr, long int); + break; +#ifdef wxLongLong_t + case wxPAT_LONGLONGINT: + p->pad_longlongint = va_arg(argptr, wxLongLong_t); + break; +#endif // wxLongLong_t + case wxPAT_SIZET: + p->pad_sizet = va_arg(argptr, size_t); + break; + case wxPAT_DOUBLE: + p->pad_double = va_arg(argptr, double); + break; + case wxPAT_LONGDOUBLE: + p->pad_longdouble = va_arg(argptr, long double); + break; + case wxPAT_POINTER: + p->pad_pointer = va_arg(argptr, void *); + break; + + case wxPAT_CHAR: + p->pad_char = (char)va_arg(argptr, int); // char is promoted to int when passed through '...' + break; + case wxPAT_WCHAR: + p->pad_wchar = (wchar_t)va_arg(argptr, int); // char is promoted to int when passed through '...' + break; + + case wxPAT_PCHAR: + case wxPAT_PWCHAR: + p->pad_str = va_arg(argptr, void *); + break; + + case wxPAT_NINT: + p->pad_nint = va_arg(argptr, int *); + break; + case wxPAT_NSHORTINT: + p->pad_nshortint = va_arg(argptr, short int *); + break; + case wxPAT_NLONGINT: + p->pad_nlongint = va_arg(argptr, long int *); + break; + + case wxPAT_INVALID: + default: + return false; + } + + return true; // loading was successful +} + +template +int wxPrintfConvSpec::Process(CharType *buf, size_t lenMax, wxPrintfArg *p, size_t written) +{ + // buffer to avoid dynamic memory allocation each time for small strings; + // note that this buffer is used only to hold results of number formatting, + // %s directly writes user's string in buf, without using szScratch + char szScratch[wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN]; + size_t lenScratch = 0, lenCur = 0; + +#define APPEND_CH(ch) \ + { \ + if ( lenCur == lenMax ) \ + return -1; \ + \ + buf[lenCur++] = ch; \ + } + + switch ( m_type ) + { + case wxPAT_INT: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_int); + break; + + case wxPAT_LONGINT: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longint); + break; + +#ifdef wxLongLong_t + case wxPAT_LONGLONGINT: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longlongint); + break; +#endif // SIZEOF_LONG_LONG + + case wxPAT_SIZET: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_sizet); + break; + + case wxPAT_LONGDOUBLE: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longdouble); + break; + + case wxPAT_DOUBLE: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_double); + break; + + case wxPAT_POINTER: + lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_pointer); + break; + + case wxPAT_CHAR: + case wxPAT_WCHAR: + { + wxUniChar ch; + if (m_type == wxPAT_CHAR) + ch = p->pad_char; + else // m_type == wxPAT_WCHAR + ch = p->pad_wchar; + + CharType val = ch; + + size_t i; + + if (!m_bAlignLeft) + for (i = 1; i < (size_t)m_nMinWidth; i++) + APPEND_CH(_T(' ')); + + APPEND_CH(val); + + if (m_bAlignLeft) + for (i = 1; i < (size_t)m_nMinWidth; i++) + APPEND_CH(_T(' ')); + } + break; + + case wxPAT_PCHAR: + case wxPAT_PWCHAR: + { + wxArgNormalizedString arg(p->pad_str); + wxString s = arg; + + if ( !arg.IsValid() && m_nMaxWidth >= 6 ) + s = wxT("(null)"); + + typename wxPrintfStringHelper::ConvertedType strbuf( + wxPrintfStringHelper::Convert(s)); + + // at this point we are sure that m_nMaxWidth is positive or + // null (see top of wxPrintfConvSpec::LoadArg) + int len = wxMin((unsigned int)m_nMaxWidth, wxStrlen(strbuf)); + + int i; + + if (!m_bAlignLeft) + { + for (i = len; i < m_nMinWidth; i++) + APPEND_CH(_T(' ')); + } + + len = wxMin((unsigned int)len, lenMax-lenCur); + wxStrncpy(buf+lenCur, strbuf, len); + lenCur += len; + + if (m_bAlignLeft) + { + for (i = len; i < m_nMinWidth; i++) + APPEND_CH(_T(' ')); + } + } + break; + + case wxPAT_NINT: + *p->pad_nint = written; + break; + + case wxPAT_NSHORTINT: + *p->pad_nshortint = (short int)written; + break; + + case wxPAT_NLONGINT: + *p->pad_nlongint = written; + break; + + case wxPAT_INVALID: + default: + return -1; + } + + // if we used system's sprintf() then we now need to append the s_szScratch + // buffer to the given one... + switch (m_type) + { + case wxPAT_INT: + case wxPAT_LONGINT: +#ifdef wxLongLong_t + case wxPAT_LONGLONGINT: +#endif + case wxPAT_SIZET: + case wxPAT_LONGDOUBLE: + case wxPAT_DOUBLE: + case wxPAT_POINTER: + wxASSERT(lenScratch < wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN); + // NB: 1) we can compare lenMax (for CharType*, i.e. possibly + // wchar_t*) with lenScratch (char*) because this code is + // formatting integers and that will have the same length + // even in UTF-8 (the only case when char* length may be + // more than wchar_t* length of the same string) + // 2) wxStrncpy converts the 2nd argument to 1st argument's + // type transparently if their types differ, so this code + // works for both instantiations + if (lenMax < lenScratch) + { + // fill output buffer and then return -1 + wxStrncpy(buf, szScratch, lenMax); + return -1; + } + wxStrncpy(buf, szScratch, lenScratch); + lenCur += lenScratch; + break; + + default: + break; // all other cases were completed previously + } + + return lenCur; +} + + +// helper that parses format string +template +struct wxPrintfConvSpecParser +{ + wxPrintfConvSpecParser(const CharType *format) + : posarg_present(false), nonposarg_present(false), + nargs(0) + { + memset(pspec, 0, sizeof(pspec)); + + const CharType *toparse = format; + + // parse the format string + for (; *toparse != wxT('\0'); toparse++) + { + if (*toparse == wxT('%') ) + { + arg[nargs].Init(); + + // let's see if this is a (valid) conversion specifier... + if (arg[nargs].Parse(toparse)) + { + // ...yes it is + wxPrintfConvSpec *current = &arg[nargs]; + + // make toparse point to the end of this specifier + toparse = current->m_pArgEnd; + + if (current->m_pos > 0) + { + // the positionals start from number 1... adjust the index + current->m_pos--; + posarg_present = true; + } + else + { + // not a positional argument... + current->m_pos = nargs; + nonposarg_present = true; + } + + // this conversion specifier is tied to the pos-th argument... + pspec[current->m_pos] = current; + nargs++; + + if (nargs == wxMAX_SVNPRINTF_ARGUMENTS) + { + wxLogDebug(wxT("A single call to wxVsnprintf() has more than %d arguments; ") + wxT("ignoring all remaining arguments."), wxMAX_SVNPRINTF_ARGUMENTS); + break; // cannot handle any additional conv spec + } + } + else + { + // it's safe to look in the next character of toparse as at + // worst we'll hit its \0 + if (*(toparse+1) == wxT('%')) + { + // the Parse() returned false because we've found a %% + toparse++; + } + } + } + } + } + + wxPrintfConvSpec arg[wxMAX_SVNPRINTF_ARGUMENTS]; + wxPrintfConvSpec *pspec[wxMAX_SVNPRINTF_ARGUMENTS]; + bool posarg_present, nonposarg_present; + unsigned nargs; +}; + +#undef APPEND_CH +#undef CHECK_PREC + +#endif // _WX_PRIVATE_WXPRINTF_H_ diff --git a/include/wx/strvararg.h b/include/wx/strvararg.h index a5445af31a..ae06ff1e5a 100644 --- a/include/wx/strvararg.h +++ b/include/wx/strvararg.h @@ -48,7 +48,9 @@ class WXDLLIMPEXP_FWD_BASE wxString; // locale is not UTF-8 // // Note that wxFormatString *must* be used for the format parameter of these -// functions, otherwise the implementation won't work correctly. +// functions, otherwise the implementation won't work correctly. Furthermore, +// it must be passed by value, not reference, because it's modified by the +// vararg templates internally. // // Parameters: // [ there are examples in square brackets showing values of the parameters @@ -138,6 +140,20 @@ public: wxFormatString(const wxWCharBuffer& str) : m_wchar(str), m_str(NULL), m_cstr(NULL) {} + + enum ArgumentType + { + Arg_Char, // character as char + + Arg_Other // something else, for example int for %d + }; + + // returns the type of format specifier for n-th variadic argument (this is + // not necessarily n-th format specifier if positional specifiers are used); + // called by wxArgNormalizer<> specializations to get information about + // n-th variadic argument desired representation + ArgumentType GetArgumentType(unsigned n) const; + #if !wxUSE_UNICODE_WCHAR operator const char*() const { return wx_const_cast(wxFormatString*, this)->AsChar(); } @@ -182,6 +198,38 @@ private: DECLARE_NO_COPY_CLASS(wxFormatString) }; +// these two helper classes are used to find wxFormatString argument among fixed +// arguments passed to a vararg template +struct wxFormatStringArgument +{ + wxFormatStringArgument(const wxFormatString *s = NULL) : m_str(s) {} + const wxFormatString *m_str; + + // overriding this operator allows us to reuse _WX_VARARG_JOIN macro + wxFormatStringArgument operator,(const wxFormatStringArgument& a) const + { + wxASSERT_MSG( m_str == NULL || a.m_str == NULL, + "can't have two format strings in vararg function" ); + return wxFormatStringArgument(m_str ? m_str : a.m_str); + } + + operator const wxFormatString*() const { return m_str; } +}; + +template +inline wxFormatStringArgument wxFindFormatStringArgument(T WXUNUSED(arg)) +{ + // by default, arguments are not format strings, so return "not found" + return wxFormatStringArgument(); +} + +inline wxFormatStringArgument +wxFindFormatStringArgument(const wxFormatString& arg) +{ + return wxFormatStringArgument(&arg); +}; + + // ---------------------------------------------------------------------------- // wxArgNormalizer* converters // ---------------------------------------------------------------------------- @@ -192,7 +240,13 @@ private: template struct wxArgNormalizer { - wxArgNormalizer(const T& value) : m_value(value) {} + // Ctor. 'value' is the value passed as variadic argument, 'fmt' is pointer + // to printf-like format string or NULL if the variadic function doesn't + // use format string and 'index' is index of 'value' in variadic arguments + // list (starting at 1) + wxArgNormalizer(T value, + const wxFormatString *WXUNUSED(fmt), unsigned WXUNUSED(index)) + : m_value(value) {} // Returns the value in a form that can be safely passed to real vararg // functions. In case of strings, this is char* in ANSI build and wchar_t* @@ -209,7 +263,9 @@ struct wxArgNormalizer template struct wxArgNormalizerWchar : public wxArgNormalizer { - wxArgNormalizerWchar(const T& value) : wxArgNormalizer(value) {} + wxArgNormalizerWchar(T value, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizer(value, fmt, index) {} }; #endif // !wxUSE_UTF8_LOCALE_ONLY @@ -219,7 +275,9 @@ struct wxArgNormalizerWchar : public wxArgNormalizer template struct wxArgNormalizerUtf8 : public wxArgNormalizer { - wxArgNormalizerUtf8(const T& value) : wxArgNormalizer(value) {} + wxArgNormalizerUtf8(T value, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizer(value, fmt, index) {} }; #define wxArgNormalizerNative wxArgNormalizerUtf8 @@ -241,7 +299,10 @@ struct wxArgNormalizerWithBuffer typedef wxCharTypeBuffer CharBuffer; wxArgNormalizerWithBuffer() {} - wxArgNormalizerWithBuffer(const CharBuffer& buf) : m_value(buf) {} + wxArgNormalizerWithBuffer(const CharBuffer& buf, + const wxFormatString *WXUNUSED(fmt), + unsigned WXUNUSED(index)) + : m_value(buf) {} const CharType *get() const { return m_value; } @@ -252,7 +313,11 @@ struct wxArgNormalizerWithBuffer template<> struct WXDLLIMPEXP_BASE wxArgNormalizerNative { - wxArgNormalizerNative(const wxString& s) : m_value(s) {} + wxArgNormalizerNative(const wxString& s, + const wxFormatString *WXUNUSED(fmt), + unsigned WXUNUSED(index)) + : m_value(s) {} + const wxStringCharType *get() const; const wxString& m_value; @@ -262,7 +327,11 @@ struct WXDLLIMPEXP_BASE wxArgNormalizerNative template<> struct WXDLLIMPEXP_BASE wxArgNormalizerNative { - wxArgNormalizerNative(const wxCStrData& value) : m_value(value) {} + wxArgNormalizerNative(const wxCStrData& value, + const wxFormatString *WXUNUSED(fmt), + unsigned WXUNUSED(index)) + : m_value(value) {} + const wxStringCharType *get() const; const wxCStrData& m_value; @@ -274,14 +343,16 @@ template<> struct WXDLLIMPEXP_BASE wxArgNormalizerWchar : public wxArgNormalizerWithBuffer { - wxArgNormalizerWchar(const wxString& s); + wxArgNormalizerWchar(const wxString& s, + const wxFormatString *fmt, unsigned index); }; template<> struct WXDLLIMPEXP_BASE wxArgNormalizerWchar : public wxArgNormalizerWithBuffer { - wxArgNormalizerWchar(const wxCStrData& s); + wxArgNormalizerWchar(const wxCStrData& s, + const wxFormatString *fmt, unsigned index); }; #endif // wxUSE_UNICODE_UTF8 && !wxUSE_UTF8_LOCALE_ONLY @@ -294,8 +365,9 @@ template<> struct wxArgNormalizerWchar : public wxArgNormalizerWithBuffer { - wxArgNormalizerWchar(const char* s) - : wxArgNormalizerWithBuffer(wxConvLibc.cMB2WC(s)) {} + wxArgNormalizerWchar(const char* s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(wxConvLibc.cMB2WC(s), fmt, index) {} }; #elif wxUSE_UNICODE_UTF8 @@ -304,15 +376,18 @@ template<> struct wxArgNormalizerUtf8 : public wxArgNormalizerWithBuffer { - wxArgNormalizerUtf8(const wchar_t* s) - : wxArgNormalizerWithBuffer(wxConvUTF8.cWC2MB(s)) {} + wxArgNormalizerUtf8(const wchar_t* s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(wxConvUTF8.cWC2MB(s), fmt, index) {} }; template<> struct wxArgNormalizerUtf8 : public wxArgNormalizerWithBuffer { - wxArgNormalizerUtf8(const char* s) + wxArgNormalizerUtf8(const char* s, + const wxFormatString *WXUNUSED(fmt), + unsigned WXUNUSED(index)) { if ( wxLocaleIsUtf8 ) { @@ -336,8 +411,9 @@ template<> struct wxArgNormalizerWchar : public wxArgNormalizerWithBuffer { - wxArgNormalizerWchar(const char* s) - : wxArgNormalizerWithBuffer(wxConvLibc.cMB2WC(s)) {} + wxArgNormalizerWchar(const char* s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(wxConvLibc.cMB2WC(s), fmt, index) {} }; #endif // !wxUSE_UTF8_LOCALE_ONLY @@ -347,8 +423,9 @@ template<> struct wxArgNormalizerWchar : public wxArgNormalizerWithBuffer { - wxArgNormalizerWchar(const wchar_t* s) - : wxArgNormalizerWithBuffer(wxConvLibc.cWC2MB(s)) {} + wxArgNormalizerWchar(const wchar_t* s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(wxConvLibc.cWC2MB(s), fmt, index) {} }; #endif // wxUSE_UNICODE_WCHAR/wxUSE_UNICODE_UTF8/ANSI @@ -376,7 +453,9 @@ struct wxArgNormalizerWchar template<> \ struct Normalizer : public Normalizer \ { \ - Normalizer(BaseT value) : Normalizer(value) {} \ + Normalizer(BaseT value, \ + const wxFormatString *fmt, unsigned index) \ + : Normalizer(value, fmt, index) {} \ } // non-reference versions of specializations for string objects @@ -403,16 +482,18 @@ template<> struct wxArgNormalizerWchar : public wxArgNormalizerWchar { - wxArgNormalizerWchar(const std::string& s) - : wxArgNormalizerWchar(s.c_str()) {} + wxArgNormalizerWchar(const std::string& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWchar(s.c_str(), fmt, index) {} }; template<> struct wxArgNormalizerWchar : public wxArgNormalizerWchar { - wxArgNormalizerWchar(const wxStdWideString& s) - : wxArgNormalizerWchar(s.c_str()) {} + wxArgNormalizerWchar(const wxStdWideString& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWchar(s.c_str(), fmt, index) {} }; #endif // !wxUSE_UTF8_LOCALE_ONLY @@ -421,16 +502,18 @@ template<> struct wxArgNormalizerUtf8 : public wxArgNormalizerUtf8 { - wxArgNormalizerUtf8(const std::string& s) - : wxArgNormalizerUtf8(s.c_str()) {} + wxArgNormalizerUtf8(const std::string& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerUtf8(s.c_str(), fmt, index) {} }; template<> struct wxArgNormalizerUtf8 : public wxArgNormalizerUtf8 { - wxArgNormalizerUtf8(const wxStdWideString& s) - : wxArgNormalizerUtf8(s.c_str()) {} + wxArgNormalizerUtf8(const wxStdWideString& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerUtf8(s.c_str(), fmt, index) {} }; #endif // wxUSE_UNICODE_UTF8 @@ -441,42 +524,69 @@ WX_ARG_NORMALIZER_FORWARD(wxStdWideString, const wxStdWideString&); // versions for wxUniChar, wxUniCharRef: - -#if !wxUSE_UTF8_LOCALE_ONLY +// (this is same for UTF-8 and Wchar builds, we just convert to wchar_t) template<> -struct wxArgNormalizerWchar +struct wxArgNormalizer : public wxArgNormalizer +{ + wxArgNormalizer(const wxUniChar& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizer(s.GetValue(), fmt, index) {} +}; + +// for wchar_t, default handler does the right thing + +// char has to be treated differently in Unicode builds: a char argument may +// be used either for a character value (which should be converted into +// wxUniChar) or as an integer value (which should be left as-is). We take +// advantage of the fact that both char and wchar_t are converted into int +// in variadic arguments here. +#if wxUSE_UNICODE +template +struct wxArgNormalizerNarrowChar { - wxArgNormalizerWchar(const wxUniChar& s) : m_value(s) {} + wxArgNormalizerNarrowChar(T value, + const wxFormatString *fmt, unsigned index) + { + // FIXME-UTF8: which one is better default in absence of fmt string + // (i.e. when used like e.g. Foo("foo", "bar", 'c', NULL)? + if ( !fmt || fmt->GetArgumentType(index) == wxFormatString::Arg_Char ) + m_value = wxUniChar(value).GetValue(); + else + m_value = value; + } - // FIXME-UTF8: use wchar_t once ANSI build is removed - wxChar get() const { return m_value; } + int get() const { return m_value; } - wxChar m_value; + T m_value; }; -#endif // !wxUSE_UTF8_LOCALE_ONLY -#if wxUSE_UNICODE_UTF8 template<> -struct wxArgNormalizerUtf8 +struct wxArgNormalizer : public wxArgNormalizerNarrowChar { - wxArgNormalizerUtf8(const wxUniChar& s) : m_value(s.AsUTF8()) {} - - const wxStringCharType *get() const { return m_value; } + wxArgNormalizer(char value, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerNarrowChar(value, fmt, index) {} +}; - wxUniChar::Utf8CharBuffer m_value; +template<> +struct wxArgNormalizer + : public wxArgNormalizerNarrowChar +{ + wxArgNormalizer(unsigned char value, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerNarrowChar(value, fmt, index) {} }; -#endif // wxUSE_UNICODE_UTF8 +#endif // wxUSE_UNICODE + +// convert references: WX_ARG_NORMALIZER_FORWARD(wxUniChar, const wxUniChar&); WX_ARG_NORMALIZER_FORWARD(const wxUniCharRef&, const wxUniChar&); WX_ARG_NORMALIZER_FORWARD(wxUniCharRef, const wxUniChar&); -// convert char/wchar_t to wxUniChar to get output in the right encoding: -WX_ARG_NORMALIZER_FORWARD(char, const wxUniChar&); -WX_ARG_NORMALIZER_FORWARD(const char&, const wxUniChar&); -WX_ARG_NORMALIZER_FORWARD(unsigned char, const wxUniChar&); -WX_ARG_NORMALIZER_FORWARD(const unsigned char&, const wxUniChar&); -WX_ARG_NORMALIZER_FORWARD(wchar_t, const wxUniChar&); -WX_ARG_NORMALIZER_FORWARD(const wchar_t&, const wxUniChar&); +WX_ARG_NORMALIZER_FORWARD(const wchar_t&, wchar_t); + +WX_ARG_NORMALIZER_FORWARD(const char&, char); +WX_ARG_NORMALIZER_FORWARD(const unsigned char&, unsigned char); #undef WX_ARG_NORMALIZER_FORWARD @@ -605,7 +715,6 @@ private: #define _WX_VARARG_FIXED_UNUSED_EXPAND_4(t1,t2,t3,t4) \ t1 WXUNUSED(f1), t2 WXUNUSED(f2), t3 WXUNUSED(f3), t4 WXUNUSED(f4) - // This macro expands N-items tuple of fixed arguments types into part of // function's declaration. For example, // "_WX_VARARG_FIXED_EXPAND(3, (int, char*, int))" expands into @@ -621,7 +730,6 @@ private: #define _WX_VARARG_FIXED_UNUSED_EXPAND_IMPL(N, args) \ _WX_VARARG_FIXED_UNUSED_EXPAND_##N args - // This macro calls another macro 'm' passed as second argument 'N' times, // with its only argument set to 1..N, and concatenates the results using // comma as separator. @@ -667,13 +775,21 @@ private: // Generates code snippet for passing i-th argument of vararg function // wrapper to its implementation, normalizing it in the process: -#define _WX_VARARG_PASS_WCHAR(i) wxArgNormalizerWchar(a##i).get() -#define _WX_VARARG_PASS_UTF8(i) wxArgNormalizerUtf8(a##i).get() +#define _WX_VARARG_PASS_WCHAR(i) \ + wxArgNormalizerWchar(a##i, fmt, i).get() +#define _WX_VARARG_PASS_UTF8(i) \ + wxArgNormalizerUtf8(a##i, fmt, i).get() // And the same for fixed arguments, _not_ normalizing it: #define _WX_VARARG_PASS_FIXED(i) f##i +#define _WX_VARARG_FIND_FMT(i) (wxFindFormatStringArgument(f##i)) + +#define _WX_VARARG_FORMAT_STRING(numfixed, fixed) \ + const wxFormatString *fmt = \ + (_WX_VARARG_JOIN(numfixed, _WX_VARARG_FIND_FMT)) + #if wxUSE_UNICODE_UTF8 #define _WX_VARARG_DO_CALL_UTF8(return_kw, impl, implUtf8, N, numfixed) \ return_kw implUtf8(_WX_VARARG_JOIN(numfixed, _WX_VARARG_PASS_FIXED), \ @@ -719,6 +835,7 @@ private: rettype name(_WX_VARARG_FIXED_EXPAND(numfixed, fixed), \ _WX_VARARG_JOIN(N, _WX_VARARG_ARG)) \ { \ + _WX_VARARG_FORMAT_STRING(numfixed, fixed); \ _WX_VARARG_DO_CALL(return, impl, implUtf8, N, numfixed); \ } @@ -739,6 +856,7 @@ private: void name(_WX_VARARG_FIXED_EXPAND(numfixed, fixed), \ _WX_VARARG_JOIN(N, _WX_VARARG_ARG)) \ { \ + _WX_VARARG_FORMAT_STRING(numfixed, fixed); \ _WX_VARARG_DO_CALL(wxEMPTY_PARAMETER_VALUE, \ impl, implUtf8, N, numfixed); \ } @@ -760,6 +878,7 @@ private: name(_WX_VARARG_FIXED_EXPAND(numfixed, fixed), \ _WX_VARARG_JOIN(N, _WX_VARARG_ARG)) \ { \ + _WX_VARARG_FORMAT_STRING(numfixed, fixed); \ _WX_VARARG_DO_CALL(wxEMPTY_PARAMETER_VALUE, \ impl, implUtf8, N, numfixed); \ } diff --git a/src/common/strvararg.cpp b/src/common/strvararg.cpp index 4559748fb4..dc86bf2bb6 100644 --- a/src/common/strvararg.cpp +++ b/src/common/strvararg.cpp @@ -26,6 +26,7 @@ #include "wx/strvararg.h" #include "wx/string.h" #include "wx/crt.h" +#include "wx/private/wxprintf.h" // ============================================================================ // implementation @@ -46,13 +47,17 @@ const wxStringCharType *wxArgNormalizerNative::get() const } #if wxUSE_UNICODE_UTF8 && !wxUSE_UTF8_LOCALE_ONLY -wxArgNormalizerWchar::wxArgNormalizerWchar(const wxString& s) - : wxArgNormalizerWithBuffer(s.wc_str()) +wxArgNormalizerWchar::wxArgNormalizerWchar( + const wxString& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(s.wc_str(), fmt, index) { } -wxArgNormalizerWchar::wxArgNormalizerWchar(const wxCStrData& s) - : wxArgNormalizerWithBuffer(s.AsWCharBuf()) +wxArgNormalizerWchar::wxArgNormalizerWchar( + const wxCStrData& s, + const wxFormatString *fmt, unsigned index) + : wxArgNormalizerWithBuffer(s.AsWCharBuf(), fmt, index) { } #endif // wxUSE_UNICODE_UTF8 && !wxUSE_UTF8_LOCALE_ONLY @@ -128,10 +133,6 @@ wxArgNormalizedString::operator wxString() const And, of course, the same should be done for %c as well. - 4) Finally, in UTF-8 build when calling ANSI printf() function, we need to - translate %c to %s, because not every Unicode character can be - represented by a char. - wxScanf() family of functions is simpler, because we don't normalize their variadic arguments and we only have to handle 2) above and only for widechar @@ -436,9 +437,10 @@ class wxPrintfFormatConverterUtf8 : public wxFormatConverterBase SizeModifier WXUNUSED(size), CharType& outConv, SizeModifier& outSize) { - // added complication: %c should be translated to %s in UTF-8 build - outConv = 's'; - outSize = Size_Default; + // chars are represented using wchar_t in both builds, so this is + // the same as above + outConv = 'c'; + outSize = Size_Long; } }; #endif // wxUSE_UNICODE_UTF8 @@ -608,3 +610,50 @@ const wchar_t* wxFormatString::AsWChar() return m_convertedWChar.data(); } #endif // wxUSE_UNICODE && !wxUSE_UTF8_LOCALE_ONLY + +// ---------------------------------------------------------------------------- +// wxFormatString::GetArgumentType() +// ---------------------------------------------------------------------------- + +namespace +{ + +template +wxFormatString::ArgumentType DoGetArgumentType(const CharType *format, + unsigned n) +{ + wxCHECK_MSG( format, wxFormatString::Arg_Other, + "empty format string not allowed here" ); + + wxPrintfConvSpecParser parser(format); + + wxCHECK_MSG( parser.pspec[n-1] != NULL, wxFormatString::Arg_Other, + "requested argument not found - invalid format string?" ); + + switch ( parser.pspec[n-1]->m_type ) + { + case wxPAT_CHAR: + case wxPAT_WCHAR: + return wxFormatString::Arg_Char; + + default: + return wxFormatString::Arg_Other; + } +} + +} // anonymous namespace + +wxFormatString::ArgumentType wxFormatString::GetArgumentType(unsigned n) const +{ + if ( m_char ) + return DoGetArgumentType(m_char.data(), n); + else if ( m_wchar ) + return DoGetArgumentType(m_wchar.data(), n); + else if ( m_str ) + return DoGetArgumentType(m_str->wx_str(), n); + else if ( m_cstr ) + return DoGetArgumentType(m_cstr->AsInternal(), n); + + wxFAIL_MSG( "unreachable code" ); + return Arg_Other; +} diff --git a/src/common/wxprintf.cpp b/src/common/wxprintf.cpp index f1b2d3914a..f0c4c221b9 100644 --- a/src/common/wxprintf.cpp +++ b/src/common/wxprintf.cpp @@ -20,10 +20,6 @@ #pragma hdrstop #endif -#include "wx/crt.h" - -#include - #ifndef WX_PRECOMP #include "wx/string.h" #include "wx/hash.h" @@ -31,10 +27,7 @@ #include "wx/log.h" #endif -#if defined(__MWERKS__) && __MSL__ >= 0x6000 -namespace std {} -using namespace std ; -#endif +#include "wx/private/wxprintf.h" // ============================================================================ @@ -60,755 +53,6 @@ using namespace std ; #if !defined(wxCRT_VsnprintfW) || !defined(wxCRT_VsnprintfA) -// some limits of our implementation -#define wxMAX_SVNPRINTF_ARGUMENTS 64 -#define wxMAX_SVNPRINTF_FLAGBUFFER_LEN 32 -#define wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN 512 - -// prefer snprintf over sprintf -#if defined(__VISUALC__) || \ - (defined(__BORLANDC__) && __BORLANDC__ >= 0x540) - #define system_sprintf(buff, max, flags, data) \ - ::_snprintf(buff, max, flags, data) -#elif defined(HAVE_SNPRINTF) - #define system_sprintf(buff, max, flags, data) \ - ::snprintf(buff, max, flags, data) -#else // NB: at least sprintf() should always be available - // since 'max' is not used in this case, wxVsnprintf() should always - // ensure that 'buff' is big enough for all common needs - // (see wxMAX_SVNPRINTF_FLAGBUFFER_LEN and wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN) - #define system_sprintf(buff, max, flags, data) \ - ::sprintf(buff, flags, data) - - #define SYSTEM_SPRINTF_IS_UNSAFE -#endif - -namespace -{ - -// the conversion specifiers accepted by wxCRT_VsnprintfW -enum wxPrintfArgType { - wxPAT_INVALID = -1, - - wxPAT_INT, // %d, %i, %o, %u, %x, %X - wxPAT_LONGINT, // %ld, etc -#ifdef wxLongLong_t - wxPAT_LONGLONGINT, // %Ld, etc -#endif - wxPAT_SIZET, // %Zd, etc - - wxPAT_DOUBLE, // %e, %E, %f, %g, %G - wxPAT_LONGDOUBLE, // %le, etc - - wxPAT_POINTER, // %p - - wxPAT_CHAR, // %hc (in ANSI mode: %c, too) - wxPAT_WCHAR, // %lc (in Unicode mode: %c, too) - - wxPAT_PCHAR, // %s (related to a char *) - wxPAT_PWCHAR, // %s (related to a wchar_t *) - - wxPAT_NINT, // %n - wxPAT_NSHORTINT, // %hn - wxPAT_NLONGINT // %ln -}; - -// an argument passed to wxCRT_VsnprintfW -typedef union { - int pad_int; // %d, %i, %o, %u, %x, %X - long int pad_longint; // %ld, etc -#ifdef wxLongLong_t - wxLongLong_t pad_longlongint; // %Ld, etc -#endif - size_t pad_sizet; // %Zd, etc - - double pad_double; // %e, %E, %f, %g, %G - long double pad_longdouble; // %le, etc - - void *pad_pointer; // %p - - char pad_char; // %hc (in ANSI mode: %c, too) - wchar_t pad_wchar; // %lc (in Unicode mode: %c, too) - - void *pad_str; // %s - - int *pad_nint; // %n - short int *pad_nshortint; // %hn - long int *pad_nlongint; // %ln -} wxPrintfArg; - -// helper for converting string into either char* or wchar_t* dependening -// on the type of wxPrintfConvSpec instantiation: -template struct wxPrintfStringHelper {}; - -template<> struct wxPrintfStringHelper -{ - typedef const wxWX2MBbuf ConvertedType; - static ConvertedType Convert(const wxString& s) { return s.mb_str(); } -}; - -template<> struct wxPrintfStringHelper -{ - typedef const wxWX2WCbuf ConvertedType; - static ConvertedType Convert(const wxString& s) { return s.wc_str(); } -}; - - -// Contains parsed data relative to a conversion specifier given to -// wxCRT_VsnprintfW and parsed from the format string -// NOTE: in C++ there is almost no difference between struct & classes thus -// there is no performance gain by using a struct here... -template -class wxPrintfConvSpec -{ -public: - - // the position of the argument relative to this conversion specifier - size_t m_pos; - - // the type of this conversion specifier - wxPrintfArgType m_type; - - // the minimum and maximum width - // when one of this var is set to -1 it means: use the following argument - // in the stack as minimum/maximum width for this conversion specifier - int m_nMinWidth, m_nMaxWidth; - - // does the argument need to the be aligned to left ? - bool m_bAlignLeft; - - // pointer to the '%' of this conversion specifier in the format string - // NOTE: this points somewhere in the string given to the Parse() function - - // it's task of the caller ensure that memory is still valid ! - const CharType *m_pArgPos; - - // pointer to the last character of this conversion specifier in the - // format string - // NOTE: this points somewhere in the string given to the Parse() function - - // it's task of the caller ensure that memory is still valid ! - const CharType *m_pArgEnd; - - // a little buffer where formatting flags like #+\.hlqLZ are stored by Parse() - // for use in Process() - // NB: even if this buffer is used only for numeric conversion specifiers and - // thus could be safely declared as a char[] buffer, we want it to be wchar_t - // so that in Unicode builds we can avoid to convert its contents to Unicode - // chars when copying it in user's buffer. - char m_szFlags[wxMAX_SVNPRINTF_FLAGBUFFER_LEN]; - - -public: - - // we don't declare this as a constructor otherwise it would be called - // automatically and we don't want this: to be optimized, wxCRT_VsnprintfW - // calls this function only on really-used instances of this class. - void Init(); - - // Parses the first conversion specifier in the given string, which must - // begin with a '%'. Returns false if the first '%' does not introduce a - // (valid) conversion specifier and thus should be ignored. - bool Parse(const CharType *format); - - // Process this conversion specifier and puts the result in the given - // buffer. Returns the number of characters written in 'buf' or -1 if - // there's not enough space. - int Process(CharType *buf, size_t lenMax, wxPrintfArg *p, size_t written); - - // Loads the argument of this conversion specifier from given va_list. - bool LoadArg(wxPrintfArg *p, va_list &argptr); - -private: - // An helper function of LoadArg() which is used to handle the '*' flag - void ReplaceAsteriskWith(int w); -}; - -template -void wxPrintfConvSpec::Init() -{ - m_nMinWidth = 0; - m_nMaxWidth = 0xFFFF; - m_pos = 0; - m_bAlignLeft = false; - m_pArgPos = m_pArgEnd = NULL; - m_type = wxPAT_INVALID; - - // this character will never be removed from m_szFlags array and - // is important when calling sprintf() in wxPrintfConvSpec::Process() ! - m_szFlags[0] = '%'; -} - -template -bool wxPrintfConvSpec::Parse(const CharType *format) -{ - bool done = false; - - // temporary parse data - size_t flagofs = 1; - bool in_prec, // true if we found the dot in some previous iteration - prec_dot; // true if the dot has been already added to m_szFlags - int ilen = 0; - - m_bAlignLeft = in_prec = prec_dot = false; - m_pArgPos = m_pArgEnd = format; - do - { -#define CHECK_PREC \ - if (in_prec && !prec_dot) \ - { \ - m_szFlags[flagofs++] = '.'; \ - prec_dot = true; \ - } - - // what follows '%'? - const CharType ch = *(++m_pArgEnd); - switch ( ch ) - { - case wxT('\0'): - return false; // not really an argument - - case wxT('%'): - return false; // not really an argument - - case wxT('#'): - case wxT('0'): - case wxT(' '): - case wxT('+'): - case wxT('\''): - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('-'): - CHECK_PREC - m_bAlignLeft = true; - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('.'): - CHECK_PREC - in_prec = true; - prec_dot = false; - m_nMaxWidth = 0; - // dot will be auto-added to m_szFlags if non-negative - // number follows - break; - - case wxT('h'): - ilen = -1; - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('l'): - // NB: it's safe to use flagofs-1 as flagofs always start from 1 - if (m_szFlags[flagofs-1] == 'l') // 'll' modifier is the same as 'L' or 'q' - ilen = 2; - else - ilen = 1; - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('q'): - case wxT('L'): - ilen = 2; - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - break; -#ifdef __WXMSW__ - // under Windows we support the special '%I64' notation as longlong - // integer conversion specifier for MSVC compatibility - // (it behaves exactly as '%lli' or '%Li' or '%qi') - case wxT('I'): - if (*(m_pArgEnd+1) != wxT('6') || - *(m_pArgEnd+2) != wxT('4')) - return false; // bad format - - m_pArgEnd++; - m_pArgEnd++; - - ilen = 2; - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - m_szFlags[flagofs++] = '6'; - m_szFlags[flagofs++] = '4'; - break; -#endif // __WXMSW__ - - case wxT('Z'): - ilen = 3; - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('*'): - if (in_prec) - { - CHECK_PREC - - // tell Process() to use the next argument - // in the stack as maxwidth... - m_nMaxWidth = -1; - } - else - { - // tell Process() to use the next argument - // in the stack as minwidth... - m_nMinWidth = -1; - } - - // save the * in our formatting buffer... - // will be replaced later by Process() - m_szFlags[flagofs++] = char(ch); - break; - - case wxT('1'): case wxT('2'): case wxT('3'): - case wxT('4'): case wxT('5'): case wxT('6'): - case wxT('7'): case wxT('8'): case wxT('9'): - { - int len = 0; - CHECK_PREC - while ( (*m_pArgEnd >= CharType('0')) && - (*m_pArgEnd <= CharType('9')) ) - { - m_szFlags[flagofs++] = char(*m_pArgEnd); - len = len*10 + (*m_pArgEnd - wxT('0')); - m_pArgEnd++; - } - - if (in_prec) - m_nMaxWidth = len; - else - m_nMinWidth = len; - - m_pArgEnd--; // the main loop pre-increments n again - } - break; - - case wxT('$'): // a positional parameter (e.g. %2$s) ? - { - if (m_nMinWidth <= 0) - break; // ignore this formatting flag as no - // numbers are preceding it - - // remove from m_szFlags all digits previously added - do { - flagofs--; - } while (m_szFlags[flagofs] >= '1' && - m_szFlags[flagofs] <= '9'); - - // re-adjust the offset making it point to the - // next free char of m_szFlags - flagofs++; - - m_pos = m_nMinWidth; - m_nMinWidth = 0; - } - break; - - case wxT('d'): - case wxT('i'): - case wxT('o'): - case wxT('u'): - case wxT('x'): - case wxT('X'): - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - m_szFlags[flagofs] = '\0'; - if (ilen == 0) - m_type = wxPAT_INT; - else if (ilen == -1) - // NB: 'short int' value passed through '...' - // is promoted to 'int', so we have to get - // an int from stack even if we need a short - m_type = wxPAT_INT; - else if (ilen == 1) - m_type = wxPAT_LONGINT; - else if (ilen == 2) -#ifdef wxLongLong_t - m_type = wxPAT_LONGLONGINT; -#else // !wxLongLong_t - m_type = wxPAT_LONGINT; -#endif // wxLongLong_t/!wxLongLong_t - else if (ilen == 3) - m_type = wxPAT_SIZET; - done = true; - break; - - case wxT('e'): - case wxT('E'): - case wxT('f'): - case wxT('g'): - case wxT('G'): - CHECK_PREC - m_szFlags[flagofs++] = char(ch); - m_szFlags[flagofs] = '\0'; - if (ilen == 2) - m_type = wxPAT_LONGDOUBLE; - else - m_type = wxPAT_DOUBLE; - done = true; - break; - - case wxT('p'): - m_type = wxPAT_POINTER; - m_szFlags[flagofs++] = char(ch); - m_szFlags[flagofs] = '\0'; - done = true; - break; - - case wxT('c'): - if (ilen == -1) - { - // in Unicode mode %hc == ANSI character - // and in ANSI mode, %hc == %c == ANSI... - m_type = wxPAT_CHAR; - } - else if (ilen == 1) - { - // in ANSI mode %lc == Unicode character - // and in Unicode mode, %lc == %c == Unicode... - m_type = wxPAT_WCHAR; - } - else - { -#if wxUSE_UNICODE - // in Unicode mode, %c == Unicode character - m_type = wxPAT_WCHAR; -#else - // in ANSI mode, %c == ANSI character - m_type = wxPAT_CHAR; -#endif - } - done = true; - break; - - case wxT('s'): - if (ilen == -1) - { - // Unicode mode wx extension: we'll let %hs mean non-Unicode - // strings (when in ANSI mode, %s == %hs == ANSI string) - m_type = wxPAT_PCHAR; - } - else if (ilen == 1) - { - // in Unicode mode, %ls == %s == Unicode string - // in ANSI mode, %ls == Unicode string - m_type = wxPAT_PWCHAR; - } - else - { -#if wxUSE_UNICODE - m_type = wxPAT_PWCHAR; -#else - m_type = wxPAT_PCHAR; -#endif - } - done = true; - break; - - case wxT('n'): - if (ilen == 0) - m_type = wxPAT_NINT; - else if (ilen == -1) - m_type = wxPAT_NSHORTINT; - else if (ilen >= 1) - m_type = wxPAT_NLONGINT; - done = true; - break; - - default: - // bad format, don't consider this an argument; - // leave it unchanged - return false; - } - - if (flagofs == wxMAX_SVNPRINTF_FLAGBUFFER_LEN) - { - wxLogDebug(wxT("Too many flags specified for a single conversion specifier!")); - return false; - } - } - while (!done); - - return true; // parsing was successful -} - -template -void wxPrintfConvSpec::ReplaceAsteriskWith(int width) -{ - char temp[wxMAX_SVNPRINTF_FLAGBUFFER_LEN]; - - // find the first * in our flag buffer - char *pwidth = strchr(m_szFlags, '*'); - wxCHECK_RET(pwidth, _T("field width must be specified")); - - // save what follows the * (the +1 is to skip the asterisk itself!) - strcpy(temp, pwidth+1); - if (width < 0) - { - pwidth[0] = wxT('-'); - pwidth++; - } - - // replace * with the actual integer given as width -#ifndef SYSTEM_SPRINTF_IS_UNSAFE - int maxlen = (m_szFlags + wxMAX_SVNPRINTF_FLAGBUFFER_LEN - pwidth) / - sizeof(*m_szFlags); -#endif - int offset = system_sprintf(pwidth, maxlen, "%d", abs(width)); - - // restore after the expanded * what was following it - strcpy(pwidth+offset, temp); -} - -template -bool wxPrintfConvSpec::LoadArg(wxPrintfArg *p, va_list &argptr) -{ - // did the '*' width/precision specifier was used ? - if (m_nMaxWidth == -1) - { - // take the maxwidth specifier from the stack - m_nMaxWidth = va_arg(argptr, int); - if (m_nMaxWidth < 0) - m_nMaxWidth = 0; - else - ReplaceAsteriskWith(m_nMaxWidth); - } - - if (m_nMinWidth == -1) - { - // take the minwidth specifier from the stack - m_nMinWidth = va_arg(argptr, int); - - ReplaceAsteriskWith(m_nMinWidth); - if (m_nMinWidth < 0) - { - m_bAlignLeft = !m_bAlignLeft; - m_nMinWidth = -m_nMinWidth; - } - } - - switch (m_type) { - case wxPAT_INT: - p->pad_int = va_arg(argptr, int); - break; - case wxPAT_LONGINT: - p->pad_longint = va_arg(argptr, long int); - break; -#ifdef wxLongLong_t - case wxPAT_LONGLONGINT: - p->pad_longlongint = va_arg(argptr, wxLongLong_t); - break; -#endif // wxLongLong_t - case wxPAT_SIZET: - p->pad_sizet = va_arg(argptr, size_t); - break; - case wxPAT_DOUBLE: - p->pad_double = va_arg(argptr, double); - break; - case wxPAT_LONGDOUBLE: - p->pad_longdouble = va_arg(argptr, long double); - break; - case wxPAT_POINTER: - p->pad_pointer = va_arg(argptr, void *); - break; - - case wxPAT_CHAR: - p->pad_char = (char)va_arg(argptr, int); // char is promoted to int when passed through '...' - break; - case wxPAT_WCHAR: - p->pad_wchar = (wchar_t)va_arg(argptr, int); // char is promoted to int when passed through '...' - break; - - case wxPAT_PCHAR: - case wxPAT_PWCHAR: - p->pad_str = va_arg(argptr, void *); - break; - - case wxPAT_NINT: - p->pad_nint = va_arg(argptr, int *); - break; - case wxPAT_NSHORTINT: - p->pad_nshortint = va_arg(argptr, short int *); - break; - case wxPAT_NLONGINT: - p->pad_nlongint = va_arg(argptr, long int *); - break; - - case wxPAT_INVALID: - default: - return false; - } - - return true; // loading was successful -} - -template -int wxPrintfConvSpec::Process(CharType *buf, size_t lenMax, wxPrintfArg *p, size_t written) -{ - // buffer to avoid dynamic memory allocation each time for small strings; - // note that this buffer is used only to hold results of number formatting, - // %s directly writes user's string in buf, without using szScratch - char szScratch[wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN]; - size_t lenScratch = 0, lenCur = 0; - -#define APPEND_CH(ch) \ - { \ - if ( lenCur == lenMax ) \ - return -1; \ - \ - buf[lenCur++] = ch; \ - } - - switch ( m_type ) - { - case wxPAT_INT: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_int); - break; - - case wxPAT_LONGINT: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longint); - break; - -#ifdef wxLongLong_t - case wxPAT_LONGLONGINT: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longlongint); - break; -#endif // SIZEOF_LONG_LONG - - case wxPAT_SIZET: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_sizet); - break; - - case wxPAT_LONGDOUBLE: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_longdouble); - break; - - case wxPAT_DOUBLE: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_double); - break; - - case wxPAT_POINTER: - lenScratch = system_sprintf(szScratch, wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN, m_szFlags, p->pad_pointer); - break; - - case wxPAT_CHAR: - case wxPAT_WCHAR: - { - wxUniChar ch; - if (m_type == wxPAT_CHAR) - ch = p->pad_char; - else // m_type == wxPAT_WCHAR - ch = p->pad_wchar; - - CharType val = ch; - - size_t i; - - if (!m_bAlignLeft) - for (i = 1; i < (size_t)m_nMinWidth; i++) - APPEND_CH(_T(' ')); - - APPEND_CH(val); - - if (m_bAlignLeft) - for (i = 1; i < (size_t)m_nMinWidth; i++) - APPEND_CH(_T(' ')); - } - break; - - case wxPAT_PCHAR: - case wxPAT_PWCHAR: - { - wxArgNormalizedString arg(p->pad_str); - wxString s = arg; - - if ( !arg.IsValid() && m_nMaxWidth >= 6 ) - s = wxT("(null)"); - - typename wxPrintfStringHelper::ConvertedType strbuf( - wxPrintfStringHelper::Convert(s)); - - // at this point we are sure that m_nMaxWidth is positive or - // null (see top of wxPrintfConvSpec::LoadArg) - int len = wxMin((unsigned int)m_nMaxWidth, wxStrlen(strbuf)); - - int i; - - if (!m_bAlignLeft) - { - for (i = len; i < m_nMinWidth; i++) - APPEND_CH(_T(' ')); - } - - len = wxMin((unsigned int)len, lenMax-lenCur); - wxStrncpy(buf+lenCur, strbuf, len); - lenCur += len; - - if (m_bAlignLeft) - { - for (i = len; i < m_nMinWidth; i++) - APPEND_CH(_T(' ')); - } - } - break; - - case wxPAT_NINT: - *p->pad_nint = written; - break; - - case wxPAT_NSHORTINT: - *p->pad_nshortint = (short int)written; - break; - - case wxPAT_NLONGINT: - *p->pad_nlongint = written; - break; - - case wxPAT_INVALID: - default: - return -1; - } - - // if we used system's sprintf() then we now need to append the s_szScratch - // buffer to the given one... - switch (m_type) - { - case wxPAT_INT: - case wxPAT_LONGINT: -#ifdef wxLongLong_t - case wxPAT_LONGLONGINT: -#endif - case wxPAT_SIZET: - case wxPAT_LONGDOUBLE: - case wxPAT_DOUBLE: - case wxPAT_POINTER: - wxASSERT(lenScratch < wxMAX_SVNPRINTF_SCRATCHBUFFER_LEN); - // NB: 1) we can compare lenMax (for CharType*, i.e. possibly - // wchar_t*) with lenScratch (char*) because this code is - // formatting integers and that will have the same length - // even in UTF-8 (the only case when char* length may be - // more than wchar_t* length of the same string) - // 2) wxStrncpy converts the 2nd argument to 1st argument's - // type transparently if their types differ, so this code - // works for both instantiations - if (lenMax < lenScratch) - { - // fill output buffer and then return -1 - wxStrncpy(buf, szScratch, lenMax); - return -1; - } - wxStrncpy(buf, szScratch, lenScratch); - lenCur += lenScratch; - break; - - default: - break; // all other cases were completed previously - } - - return lenCur; -} // Copy chars from source to dest converting '%%' to '%'. Takes at most maxIn // chars from source and write at most outMax chars to dest, returns the @@ -854,71 +98,16 @@ static int wxDoVsnprintf(CharType *buf, size_t lenMax, wprintf(L"Using wxCRT_VsnprintfW\n"); #endif - // required memory: - wxPrintfConvSpec arg[wxMAX_SVNPRINTF_ARGUMENTS]; + wxPrintfConvSpecParser parser(format); + wxPrintfArg argdata[wxMAX_SVNPRINTF_ARGUMENTS]; - wxPrintfConvSpec *pspec[wxMAX_SVNPRINTF_ARGUMENTS] = { NULL }; size_t i; // number of characters in the buffer so far, must be less than lenMax size_t lenCur = 0; - size_t nargs = 0; - const CharType *toparse = format; - - // parse the format string - bool posarg_present = false, nonposarg_present = false; - for (; *toparse != wxT('\0'); toparse++) - { - if (*toparse == wxT('%') ) - { - arg[nargs].Init(); - - // let's see if this is a (valid) conversion specifier... - if (arg[nargs].Parse(toparse)) - { - // ...yes it is - wxPrintfConvSpec *current = &arg[nargs]; - - // make toparse point to the end of this specifier - toparse = current->m_pArgEnd; - - if (current->m_pos > 0) - { - // the positionals start from number 1... adjust the index - current->m_pos--; - posarg_present = true; - } - else - { - // not a positional argument... - current->m_pos = nargs; - nonposarg_present = true; - } - - // this conversion specifier is tied to the pos-th argument... - pspec[current->m_pos] = current; - nargs++; - - if (nargs == wxMAX_SVNPRINTF_ARGUMENTS) - { - wxLogDebug(wxT("A single call to wxVsnprintf() has more than %d arguments; ") - wxT("ignoring all remaining arguments."), wxMAX_SVNPRINTF_ARGUMENTS); - break; // cannot handle any additional conv spec - } - } - else - { - // it's safe to look in the next character of toparse as at worst - // we'll hit its \0 - if (*(toparse+1) == wxT('%')) - toparse++; // the Parse() returned false because we've found a %% - } - } - } - - if (posarg_present && nonposarg_present) + if (parser.posarg_present && parser.nonposarg_present) { buf[0] = 0; return -1; // format strings with both positional and @@ -931,12 +120,12 @@ static int wxDoVsnprintf(CharType *buf, size_t lenMax, wxVaCopy(ap, argptr); // now load arguments from stack - for (i=0; i < nargs && ok; i++) + for (i=0; i < parser.nargs && ok; i++) { // !pspec[i] means that the user forgot a positional parameter (e.g. %$1s %$3s); // LoadArg == false means that wxPrintfConvSpec::Parse failed to set the // conversion specifier 'type' to a valid value... - ok = pspec[i] && pspec[i]->LoadArg(&argdata[i], ap); + ok = parser.pspec[i] && parser.pspec[i]->LoadArg(&argdata[i], ap); } va_end(ap); @@ -951,7 +140,7 @@ static int wxDoVsnprintf(CharType *buf, size_t lenMax, // finally, process each conversion specifier with its own argument toparse = format; - for (i=0; i < nargs; i++) + for (i=0; i < parser.nargs; i++) { // copy in the output buffer the portion of the format string between // last specifier and the current one @@ -1001,9 +190,6 @@ static int wxDoVsnprintf(CharType *buf, size_t lenMax, return lenCur; } -#undef APPEND_CH -#undef CHECK_PREC - } // anonymous namespace #endif // !defined(wxCRT_VsnprintfW) || !defined(wxCRT_VsnprintfA) diff --git a/tests/strings/vararg.cpp b/tests/strings/vararg.cpp index de259e3534..556dd74ca2 100644 --- a/tests/strings/vararg.cpp +++ b/tests/strings/vararg.cpp @@ -36,6 +36,7 @@ public: private: CPPUNIT_TEST_SUITE( VarArgTestCase ); CPPUNIT_TEST( StringPrintf ); + CPPUNIT_TEST( CharPrintf ); #if wxUSE_STD_STRING CPPUNIT_TEST( StdString ); #endif @@ -43,6 +44,7 @@ private: CPPUNIT_TEST_SUITE_END(); void StringPrintf(); + void CharPrintf(); #if wxUSE_STD_STRING void StdString(); #endif @@ -91,7 +93,34 @@ void VarArgTestCase::StringPrintf() // literal: bool cond = true; s2.Printf(_T("foo %s"), !cond ? s.c_str() : _T("bar")); +} + +void VarArgTestCase::CharPrintf() +{ + wxString foo("foo"); + wxString s; + + // test using wchar_t: + s.Printf("char=%c", L'c'); + WX_ASSERT_STR_EQUAL( "char=c", s ); + + // test wxUniCharRef: + s.Printf("string[1] is %c", foo[1]); + WX_ASSERT_STR_EQUAL( "string[1] is o", s ); + + // test char + char c = 'z'; + s.Printf("%c to %c", 'a', c); + WX_ASSERT_STR_EQUAL( "a to z", s ); + + // test char used as integer: + c = 240; + s.Printf("value is %i (int)", c); + WX_ASSERT_STR_EQUAL( wxString("value is -16 (int)"), s ); + unsigned char u = 240; + s.Printf("value is %i (int)", u); + WX_ASSERT_STR_EQUAL( wxString("value is 240 (int)"), s ); } #if wxUSE_STD_STRING -- 2.45.2