Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
wprintf.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * implementation of sys_vswprintf.
25  */
26 
27 #include "precompiled.h"
28 
29 #if ARCH_IA32
30 
31 /*
32  See http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html
33  for the specification (apparently an extension to ISO C) that was used
34  when creating this code.
35 */
36 
37 /*
38  Added features (compared to MSVC's printf):
39  Positional parameters (e.g. "%1$d", where '1' means '1st in the parameter list')
40  %lld (equivalent to %I64d in MSVC7.1, though it's supported natively by MSVC8)
41 
42  Unsupported features (compared to a perfect implementation):
43  ' <-- because MSVC doesn't support it
44  %S }
45  %C } because they're unnecessary and can cause confusion
46  * <-- probably works in some situations, but don't expect it to
47  *m$ <-- positional precision parameters, because they're not worthwhile
48  portability <-- just use GCC
49  efficiency <-- nothing in the spec says it should be as fast as possible
50  (the code could be made a lot faster if speed mattered more
51  than non-fixed size buffers)
52 
53 */
54 
55 #ifndef _UNICODE
56 #define _UNICODE
57 #endif
58 #include <tchar.h>
59 #ifdef _UNICODE
60 # define tstring wstring
61 # define tstringstream wstringstream
62 #else
63 # define tstring string
64 # define tstringstream stringstream
65 #endif
66 #include <string>
67 #include <vector>
68 
69 #include <stdio.h>
70 #include <sstream>
71 #include <stdarg.h>
72 
73 #if MSC_VERSION < 1400
74 # define USE_I64_FORMAT 1
75 #else
76 # define USE_I64_FORMAT 0
77 #endif
78 
79 enum
80 {
81  SPECFLAG_THOUSANDS = 1, // '
82  SPECFLAG_LEFTJUSTIFIED = 2, // -
83  SPECFLAG_SIGNED = 4, // +
84  SPECFLAG_SPACEPREFIX = 8, // <space>
85  SPECFLAG_ALTERNATE = 16, // #
86  SPECFLAG_ZEROPAD = 32 // 0
87 };
88 
89 struct FormatChunk
90 {
91  virtual ~FormatChunk() { }
92  virtual int ChunkType() = 0; // 0 = FormatSpecification, 1 = FormatString
93 };
94 
95 struct FormatVariable : public FormatChunk
96 {
97  int ChunkType() { return 0; }
98  int position; // undefined if the format includes no positional elements
99  char flags; // ['-+ #0]
100  int width; // -1 for *, 0 for unspecified
101  int precision; // -1 for *
102  int length; // "\0\0\0l", "\0\0hh", etc
103  char type; // 'd', etc
104 };
105 
106 struct FormatString : public FormatChunk
107 {
108  int ChunkType() { return 1; }
109  FormatString(std::tstring t) : text(t) {}
110  std::tstring text;
111 };
112 
113 
114 int get_flag(TCHAR c)
115 {
116  switch (c)
117  {
118  case _T('\''): return SPECFLAG_THOUSANDS;
119  case _T('-'): return SPECFLAG_LEFTJUSTIFIED;
120  case _T('+'): return SPECFLAG_SIGNED;
121  case _T(' '): return SPECFLAG_SPACEPREFIX;
122  case _T('#'): return SPECFLAG_ALTERNATE;
123  case _T('0'): return SPECFLAG_ZEROPAD;
124  }
125  return 0;
126 }
127 
128 std::tstring flags_to_string(char flags)
129 {
130  std::tstring s;
131  const char* c = "\'-+ #0";
132  for (int i=0; i<6; ++i)
133  if (flags & (1<<i))
134  s += c[i];
135  return s;
136 }
137 
138 template<typename T>
139 std::tstring to_string(T n)
140 {
141  std::tstring s;
142  std::tstringstream str;
143  str << n;
144  str >> s;
145  return s;
146 }
147 
148 int is_lengthmod(TCHAR c)
149 {
150  return
151  c == _T('h') ||
152  c == _T('l') ||
153  c == _T('j') ||
154  c == _T('z') ||
155  c == _T('t') ||
156  c == _T('L');
157 }
158 
159 // _T2('l') == 'll'
160 #define _T2(a) ((a<<8) | a)
161 
162 int type_size(TCHAR type, int length)
163 {
164  switch (type)
165  {
166  case 'd':
167  case 'i':
168  switch (length)
169  {
170  case _T ('l'): return sizeof(long);
171  case _T2('l'): return sizeof(long long);
172  case _T ('h'): return sizeof(short);
173  case _T2('h'): return sizeof(char);
174  default: return sizeof(int);
175  }
176  case 'o':
177  case 'u':
178  case 'x':
179  case 'X': return sizeof(unsigned);
180  case 'f':
181  case 'F':
182  case 'e':
183  case 'E':
184  case 'g':
185  case 'G':
186  case 'a':
187  case 'A':
188  if (length == _T('L'))
189  return sizeof(long double);
190  else
191  return sizeof(double);
192 
193  case 'c':
194  // "%lc" is a wide character, passed as a wint_t (ushort)
195  if (length == _T('l'))
196  return sizeof(wint_t);
197  // "%c" is an int, apparently
198  else
199  return sizeof(int);
200 
201  case 's':
202  if (length == _T('l'))
203  return sizeof(wchar_t*);
204  else
205  return sizeof(char*);
206 
207  case 'p':
208  return sizeof(void*);
209 
210  case 'n':
211  return sizeof(int*);
212 
213  }
214  return 0;
215 }
216 
217 int sys_vswprintf(TCHAR* buffer, size_t count, const TCHAR* format, va_list argptr)
218 {
219  // To help quickly detect incorrect 'count' values, fill the buffer with 0s
220  memset(buffer, 0, count*sizeof(TCHAR));
221 
222  /*
223 
224  Format 'variable' specifications are (in pseudo-Perl regexp syntax):
225 
226  % (\d+$)? ['-+ #0]? (* | \d+)? (. (* | \d*) )? (hh|h|l|ll|j|z|t|L)? [diouxXfFeEgGaAcspnCS%]
227  position flags width precision length type
228 
229  */
230 
231  /**** Parse the format string into constant/variable chunks ****/
232 
233  std::vector<FormatChunk*> specs;
234  std::tstring stringchunk;
235 
236  TCHAR chr;
237 
238 #define readchar(x) if ((x = *format++) == '\0') { delete s; goto finished_reading; }
239 
240  while ((chr = *format++) != '\0')
241  {
242  if (chr == _T('%'))
243  {
244  // Handle %% correctly
245  if (*format == _T('%'))
246  {
247  stringchunk += _T('%');
248  continue;
249  }
250 
251  // End the current string and start a new spec chunk
252  if (stringchunk.length())
253  {
254  specs.push_back(new FormatString(stringchunk));
255  stringchunk = _T("");
256  }
257 
258  FormatVariable *s = new FormatVariable;
259  s->position = -1;
260  s->flags = 0;
261  s->width = 0;
262  s->precision = 0;
263  s->length = 0;
264  s->type = 0;
265 
266  // Read either the position or the width
267  int number = 0;
268 
269  while (1)
270  {
271  readchar(chr);
272 
273  // Read flags (but not if it's a 0 appearing after other digits)
274  if (!number && get_flag(chr))
275  s->flags = (char)(s->flags|get_flag(chr));
276 
277  // Read decimal numbers (position or width)
278  else if (isdigit(chr))
279  number = number*10 + (chr-'0');
280 
281  // If we've reached a $, 'number' was the position,
282  // so remember it and start getting the width
283  else if (chr == _T('$'))
284  {
285  s->position = number;
286  number = 0;
287  }
288 
289  // End of the number section
290  else
291  {
292  // Remember the width
293  s->width = number;
294 
295  // Start looking for a precision
296  if (chr == _T('.'))
297  {
298  // Found a precision: read the digits
299 
300  number = 0;
301 
302  while (1)
303  {
304  readchar(chr);
305 
306  if (isdigit(chr))
307  number = number*10 + (chr-'0');
308  else
309  {
310  s->precision = number;
311  break;
312  }
313  }
314  }
315 
316  // Finished dealing with any precision.
317 
318  // Now check for length and type codes.
319 
320  if (chr == _T('I'))
321  {
322  DEBUG_WARN_ERR(ERR::LOGIC); // MSVC-style \"%I64\" is not allowed!
323  }
324 
325  if (is_lengthmod(chr))
326  {
327  s->length = chr;
328 
329  // Check for ll and hh
330  if (chr == _T('l') || chr == _T('h'))
331  {
332  if (*format == chr)
333  {
334  s->length |= (chr << 8);
335  ++format;
336  }
337  }
338 
339  readchar(chr);
340  }
341 
342  s->type = (char)chr;
343 
344  specs.push_back(s);
345 
346  break;
347  }
348  }
349  }
350  else
351  {
352  stringchunk += chr;
353  }
354  }
355 
356 #undef readchar
357 
358 finished_reading:
359 
360  if (stringchunk.length())
361  {
362  specs.push_back(new FormatString(stringchunk));
363  stringchunk = _T("");
364  }
365 
366  /**** Build a new format string (to pass to the system printf) ****/
367 
368  std::tstring newformat;
369 
370  std::vector<int> varsizes; // stores the size of each variable type, to allow stack twiddling
371 
372  typedef std::vector<FormatChunk*>::iterator ChunkIt;
373 
374  for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
375  {
376  if ((*it)->ChunkType() == 0)
377  {
378  FormatVariable* s = static_cast<FormatVariable*>(*it);
379 
380  if (s->position > 0)
381  {
382  // Grow if necessary
383  if (s->position >= (int)varsizes.size())
384  varsizes.resize(s->position+1, -1);
385  // Store the size of the current type
386  varsizes[s->position] = type_size(s->type, s->length);
387  }
388 
389 
390  newformat += _T("%");
391 
392  if (s->flags)
393  newformat += flags_to_string(s->flags);
394 
395  if (s->width == -1)
396  newformat += '*';
397  else if (s->width)
398  newformat += to_string(s->width);
399 
400  if (s->precision)
401  {
402  newformat += '.';
403  if (s->precision == -1)
404  newformat += '*';
405  else
406  newformat += to_string(s->precision);
407  }
408 
409  if (s->length)
410  {
411  if (s->length > 256)
412  {
413  if (s->length == 0x00006c6c)
414  #if USE_I64_FORMAT
415  newformat += "I64";
416  #else
417  newformat += _T("ll");
418  #endif
419  else if (s->length == 0x00006868)
420  newformat += _T("hh");
421  }
422  else
423  {
424  newformat += (char) s->length;
425  }
426  }
427 
428  newformat += s->type;
429  }
430  else
431  {
432  FormatString* s = static_cast<FormatString*>(*it);
433  newformat += s->text;
434  }
435  }
436 
437 
438 /*
439  varargs on x86:
440 
441  All types are padded to 4 bytes, so size-in-stack == _INTSIZEOF(type).
442  No special alignment is required.
443 
444  first+_INTSIZEOF(first) == first item in stack
445 
446  Keep adding _INTSIZEOF(item) to get the next item
447 */
448 
449 // Because of those dangerous assumptions about varargs:
450 #if !ARCH_IA32
451 #error SLIGHTLY FATAL ERROR: Only x86 is supported!
452 #endif
453 
454  // Highly efficient buffer to store the rearranged copy of the stack
455  std::string newstack;
456 
457  std::vector< std::pair<char*, char*> > stackitems;
458 
459  va_list arglist = argptr;
460  //va_start(arglist, format);
461 
462  const u8* newstackptr;
463 
464  if (varsizes.size())
465  {
466 
467  for (size_t i = 1; i < varsizes.size(); ++i)
468  {
469  if (varsizes[i] <= 0)
470  {
471  DEBUG_WARN_ERR(ERR::LOGIC); // Invalid variable type somewhere - make sure all variable things are positional and defined
472  return -1;
473  }
474 
475  // Based on _INTSIZEOF in stdarg.h:
476  // (Warning - slightly non-portable. But we use gcc's default printf
477  // when portability matters.)
478  #define INTSIZE(n) ( (n + sizeof(int) - 1) & ~(sizeof(int) - 1) )
479 
480  size_t size = INTSIZE(varsizes[i]);
481  stackitems.push_back( std::pair<char*, char*>( arglist, arglist+size ));
482  arglist += size;
483  }
484 
485 
486  for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
487  {
488  FormatChunk* chunk = *it;
489  if (chunk->ChunkType() == 0)
490  {
491  FormatVariable* s = static_cast<FormatVariable*>(chunk);
492  if (s->position <= 0)
493  {
494  DEBUG_WARN_ERR(ERR::LOGIC); // Invalid use of positional elements - make sure all variable things are positional and defined
495  return -1;
496  }
497  newstack += std::string( stackitems[s->position-1].first, stackitems[s->position-1].second );
498  }
499  }
500 
501  newstackptr = (const u8*)newstack.c_str();
502  }
503  else
504  {
505  newstackptr = (const u8*)arglist;
506  }
507 
508  for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
509  if ((*it)->ChunkType() == 0)
510  delete static_cast<FormatVariable*>(*it);
511  else
512  delete static_cast<FormatString*>(*it);
513 
514  int ret = _vsntprintf(buffer, count, newformat.c_str(), (va_list)newstackptr);
515 
516  // For consistency with GCC's vsnprintf, make sure the buffer is null-terminated
517  // and return an error if that truncates the output
518  if(count > 0)
519  buffer[count-1] = '\0';
520  if (ret == (int)count)
521  return -1;
522 
523  return ret;
524 }
525 
526 #endif
#define u8
Definition: types.h:39
const Status LOGIC
Definition: status.h:409
#define T(string_literal)
Definition: secure_crt.cpp:70
#define DEBUG_WARN_ERR(status)
display the error dialog with text corresponding to the given error code.
Definition: debug.h:331
int sys_vswprintf(wchar_t *buffer, size_t count, const wchar_t *format, va_list argptr)
sys_vswprintf: doesn&#39;t quite follow the standard for vswprintf, but works better across compilers: ...
Definition: printf.cpp:30