[CRT][USER32] fix wsprintf additional argument (%*s) (#1470)
[reactos.git] / sdk / lib / crt / printf / streamout.c
1 /*
2 * COPYRIGHT: GNU GPL, see COPYING in the top level directory
3 * PROJECT: ReactOS crt library
4 * FILE: lib/sdk/crt/printf/streamout.c
5 * PURPOSE: Implementation of streamout
6 * PROGRAMMERS: Timo Kreuzer
7 * Katayama Hirofumi MZ
8 */
9
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <tchar.h>
13 #include <strings.h>
14 #include <math.h>
15 #include <float.h>
16
17 #ifdef _UNICODE
18 # define streamout wstreamout
19 # define format_float format_floatw
20 #endif
21
22 #define MB_CUR_MAX 10
23 #define BUFFER_SIZE (32 + 17)
24
25 int mbtowc(wchar_t *wchar, const char *mbchar, size_t count);
26 int wctomb(char *mbchar, wchar_t wchar);
27
28 typedef struct _STRING
29 {
30 unsigned short Length;
31 unsigned short MaximumLength;
32 void *Buffer;
33 } STRING;
34
35 enum
36 {
37 /* Formatting flags */
38 FLAG_ALIGN_LEFT = 0x01,
39 FLAG_FORCE_SIGN = 0x02,
40 FLAG_FORCE_SIGNSP = 0x04,
41 FLAG_PAD_ZERO = 0x08,
42 FLAG_SPECIAL = 0x10,
43
44 /* Data format flags */
45 FLAG_SHORT = 0x100,
46 FLAG_LONG = 0x200,
47 FLAG_WIDECHAR = FLAG_LONG,
48 FLAG_INT64 = 0x400,
49 #ifdef _WIN64
50 FLAG_INTPTR = FLAG_INT64,
51 #else
52 FLAG_INTPTR = 0,
53 #endif
54 FLAG_LONGDOUBLE = 0x800,
55 };
56
57 #define va_arg_f(argptr, flags) \
58 (flags & FLAG_INT64) ? va_arg(argptr, __int64) : \
59 (flags & FLAG_SHORT) ? (short)va_arg(argptr, int) : \
60 va_arg(argptr, int)
61
62 #define va_arg_fu(argptr, flags) \
63 (flags & FLAG_INT64) ? va_arg(argptr, unsigned __int64) : \
64 (flags & FLAG_SHORT) ? (unsigned short)va_arg(argptr, int) : \
65 va_arg(argptr, unsigned int)
66
67 #define va_arg_ffp(argptr, flags) \
68 (flags & FLAG_LONGDOUBLE) ? va_arg(argptr, long double) : \
69 va_arg(argptr, double)
70
71 #define get_exp(f) (int)floor(f == 0 ? 0 : (f >= 0 ? log10(f) : log10(-f)))
72 #define round(x) floor((x) + 0.5)
73
74 #ifndef _USER32_WSPRINTF
75
76 void
77 #ifdef _LIBCNT_
78 /* Due to restrictions in kernel mode regarding the use of floating point,
79 we prevent it from being inlined */
80 __declspec(noinline)
81 #endif
82 format_float(
83 TCHAR chr,
84 unsigned int flags,
85 int precision,
86 TCHAR **string,
87 const TCHAR **prefix,
88 va_list *argptr)
89 {
90 static const TCHAR digits_l[] = _T("0123456789abcdef0x");
91 static const TCHAR digits_u[] = _T("0123456789ABCDEF0X");
92 static const TCHAR _nan[] = _T("#QNAN");
93 static const TCHAR _infinity[] = _T("#INF");
94 const TCHAR *digits = digits_l;
95 int exponent = 0, sign;
96 long double fpval, fpval2;
97 int padding = 0, num_digits, val32, base = 10;
98
99 /* Normalize the precision */
100 if (precision < 0) precision = 6;
101 else if (precision > 17)
102 {
103 padding = precision - 17;
104 precision = 17;
105 }
106
107 /* Get the float value and calculate the exponent */
108 fpval = va_arg_ffp(*argptr, flags);
109 exponent = get_exp(fpval);
110 sign = fpval < 0 ? -1 : 1;
111
112 switch (chr)
113 {
114 case _T('G'):
115 digits = digits_u;
116 case _T('g'):
117 if (precision > 0) precision--;
118 if (exponent < -4 || exponent >= precision) goto case_e;
119
120 /* Shift the decimal point and round */
121 fpval2 = round(sign * fpval * pow(10., precision));
122
123 /* Skip trailing zeroes */
124 while (precision && (unsigned __int64)fpval2 % 10 == 0)
125 {
126 precision--;
127 fpval2 /= 10;
128 }
129 break;
130
131 case _T('E'):
132 digits = digits_u;
133 case _T('e'):
134 case_e:
135 /* Shift the decimal point and round */
136 fpval2 = round(sign * fpval * pow(10., precision - exponent));
137
138 /* Compensate for changed exponent through rounding */
139 if (fpval2 >= (unsigned __int64)pow(10., precision + 1))
140 {
141 exponent++;
142 fpval2 = round(sign * fpval * pow(10., precision - exponent));
143 }
144
145 val32 = exponent >= 0 ? exponent : -exponent;
146
147 // FIXME: handle length of exponent field:
148 // http://msdn.microsoft.com/de-de/library/0fatw238%28VS.80%29.aspx
149 num_digits = 3;
150 while (num_digits--)
151 {
152 *--(*string) = digits[val32 % 10];
153 val32 /= 10;
154 }
155
156 /* Sign for the exponent */
157 *--(*string) = exponent >= 0 ? _T('+') : _T('-');
158
159 /* Add 'e' or 'E' separator */
160 *--(*string) = digits[0xe];
161 break;
162
163 case _T('A'):
164 digits = digits_u;
165 case _T('a'):
166 // base = 16;
167 // FIXME: TODO
168
169 case _T('f'):
170 default:
171 /* Shift the decimal point and round */
172 fpval2 = round(sign * fpval * pow(10., precision));
173 break;
174 }
175
176 /* Handle sign */
177 if (fpval < 0)
178 {
179 *prefix = _T("-");
180 }
181 else if (flags & FLAG_FORCE_SIGN)
182 *prefix = _T("+");
183 else if (flags & FLAG_FORCE_SIGNSP)
184 *prefix = _T(" ");
185
186 /* Handle special cases first */
187 if (_isnan(fpval))
188 {
189 (*string) -= sizeof(_nan) / sizeof(TCHAR) - 1;
190 _tcscpy((*string), _nan);
191 fpval2 = 1;
192 }
193 else if (!_finite(fpval))
194 {
195 (*string) -= sizeof(_infinity) / sizeof(TCHAR) - 1;
196 _tcscpy((*string), _infinity);
197 fpval2 = 1;
198 }
199 else
200 {
201 /* Zero padding */
202 while (padding-- > 0) *--(*string) = _T('0');
203
204 /* Digits after the decimal point */
205 num_digits = precision;
206 while (num_digits-- > 0)
207 {
208 *--(*string) = digits[(unsigned __int64)fpval2 % 10];
209 fpval2 /= base;
210 }
211 }
212
213 if (precision > 0 || flags & FLAG_SPECIAL)
214 *--(*string) = _T('.');
215
216 /* Digits before the decimal point */
217 do
218 {
219 *--(*string) = digits[(unsigned __int64)fpval2 % base];
220 fpval2 /= base;
221 }
222 while ((unsigned __int64)fpval2);
223
224 }
225 #endif
226
227 static
228 int
229 streamout_char(FILE *stream, int chr)
230 {
231 #if !defined(_USER32_WSPRINTF)
232 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
233 return 1;
234 #endif
235 #if defined(_USER32_WSPRINTF) || defined(_LIBCNT_)
236 /* Check if the buffer is full */
237 if (stream->_cnt < sizeof(TCHAR))
238 return 0;
239
240 *(TCHAR*)stream->_ptr = chr;
241 stream->_ptr += sizeof(TCHAR);
242 stream->_cnt -= sizeof(TCHAR);
243
244 return 1;
245 #else
246 return _fputtc((TCHAR)chr, stream) != _TEOF;
247 #endif
248 }
249
250 static
251 int
252 streamout_astring(FILE *stream, const char *string, size_t count)
253 {
254 TCHAR chr;
255 int written = 0;
256
257 #if !defined(_USER32_WSPRINTF)
258 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
259 return count;
260 #endif
261
262 while (count--)
263 {
264 #ifdef _UNICODE
265 int len;
266 if ((len = mbtowc(&chr, string, MB_CUR_MAX)) < 1) break;
267 string += len;
268 #else
269 chr = *string++;
270 #endif
271 if (streamout_char(stream, chr) == 0) return -1;
272 written++;
273 }
274
275 return written;
276 }
277
278 static
279 int
280 streamout_wstring(FILE *stream, const wchar_t *string, size_t count)
281 {
282 wchar_t chr;
283 int written = 0;
284
285 #if defined(_UNICODE) && !defined(_USER32_WSPRINTF)
286 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
287 return count;
288 #endif
289
290 while (count--)
291 {
292 #ifndef _UNICODE
293 char mbchar[MB_CUR_MAX], *ptr = mbchar;
294 int mblen;
295
296 mblen = wctomb(mbchar, *string++);
297 if (mblen <= 0) return written;
298
299 while (chr = *ptr++, mblen--)
300 #else
301 chr = *string++;
302 #endif
303 {
304 if (streamout_char(stream, chr) == 0) return -1;
305 written++;
306 }
307 }
308
309 return written;
310 }
311
312 #ifdef _UNICODE
313 #define streamout_string streamout_wstring
314 #else
315 #define streamout_string streamout_astring
316 #endif
317
318 #ifdef _USER32_WSPRINTF
319 # define USE_MULTISIZE 0
320 #else
321 # define USE_MULTISIZE 1
322 #endif
323
324 int
325 __cdecl
326 streamout(FILE *stream, const TCHAR *format, va_list argptr)
327 {
328 static const TCHAR digits_l[] = _T("0123456789abcdef0x");
329 static const TCHAR digits_u[] = _T("0123456789ABCDEF0X");
330 static const char *_nullstring = "(null)";
331 TCHAR buffer[BUFFER_SIZE + 1];
332 TCHAR chr, *string;
333 STRING *nt_string;
334 const TCHAR *digits, *prefix;
335 int base, fieldwidth, precision, padding;
336 size_t prefixlen, len;
337 int written = 1, written_all = 0;
338 unsigned int flags;
339 unsigned __int64 val64;
340
341 buffer[BUFFER_SIZE] = '\0';
342
343 while (written >= 0)
344 {
345 chr = *format++;
346
347 /* Check for end of format string */
348 if (chr == _T('\0')) break;
349
350 /* Check for 'normal' character or double % */
351 if ((chr != _T('%')) ||
352 (chr = *format++) == _T('%'))
353 {
354 /* Write the character to the stream */
355 if ((written = streamout_char(stream, chr)) == 0) return -1;
356 written_all += written;
357 continue;
358 }
359
360 /* Handle flags */
361 flags = 0;
362 while (1)
363 {
364 if (chr == _T('-')) flags |= FLAG_ALIGN_LEFT;
365 else if (chr == _T('+')) flags |= FLAG_FORCE_SIGN;
366 else if (chr == _T(' ')) flags |= FLAG_FORCE_SIGNSP;
367 else if (chr == _T('0')) flags |= FLAG_PAD_ZERO;
368 else if (chr == _T('#')) flags |= FLAG_SPECIAL;
369 else break;
370 chr = *format++;
371 }
372
373 /* Handle field width modifier */
374 if (chr == _T('*'))
375 {
376 #ifdef _USER32_WSPRINTF
377 if ((written = streamout_char(stream, chr)) == 0) return -1;
378 written_all += written;
379 continue;
380 #else
381 fieldwidth = va_arg(argptr, int);
382 if (fieldwidth < 0)
383 {
384 flags |= FLAG_ALIGN_LEFT;
385 fieldwidth = -fieldwidth;
386 }
387 chr = *format++;
388 #endif
389 }
390 else
391 {
392 fieldwidth = 0;
393 while (chr >= _T('0') && chr <= _T('9'))
394 {
395 fieldwidth = fieldwidth * 10 + (chr - _T('0'));
396 chr = *format++;
397 }
398 }
399
400 /* Handle precision modifier */
401 if (chr == '.')
402 {
403 chr = *format++;
404
405 if (chr == _T('*'))
406 {
407 #ifdef _USER32_WSPRINTF
408 if ((written = streamout_char(stream, chr)) == 0) return -1;
409 written_all += written;
410 continue;
411 #else
412 precision = va_arg(argptr, int);
413 chr = *format++;
414 #endif
415 }
416 else
417 {
418 precision = 0;
419 while (chr >= _T('0') && chr <= _T('9'))
420 {
421 precision = precision * 10 + (chr - _T('0'));
422 chr = *format++;
423 }
424 }
425 }
426 else precision = -1;
427
428 /* Handle argument size prefix */
429 do
430 {
431 if (chr == _T('h')) flags |= FLAG_SHORT;
432 else if (chr == _T('w')) flags |= FLAG_WIDECHAR;
433 else if (chr == _T('L')) flags |= 0; // FIXME: long double
434 else if (chr == _T('F')) flags |= 0; // FIXME: what is that?
435 else if (chr == _T('l'))
436 {
437 /* Check if this is the 2nd 'l' in a row */
438 if (format[-2] == 'l') flags |= FLAG_INT64;
439 else flags |= FLAG_LONG;
440 }
441 else if (chr == _T('I'))
442 {
443 if (format[0] == _T('3') && format[1] == _T('2'))
444 {
445 format += 2;
446 }
447 else if (format[0] == _T('6') && format[1] == _T('4'))
448 {
449 format += 2;
450 flags |= FLAG_INT64;
451 }
452 else if (format[0] == _T('x') || format[0] == _T('X') ||
453 format[0] == _T('d') || format[0] == _T('i') ||
454 format[0] == _T('u') || format[0] == _T('o'))
455 {
456 flags |= FLAG_INTPTR;
457 }
458 else break;
459 }
460 else break;
461 chr = *format++;
462 }
463 while (USE_MULTISIZE);
464
465 /* Handle the format specifier */
466 digits = digits_l;
467 string = &buffer[BUFFER_SIZE];
468 base = 10;
469 prefix = 0;
470 switch (chr)
471 {
472 case _T('n'):
473 if (flags & FLAG_INT64)
474 *va_arg(argptr, __int64*) = written_all;
475 else if (flags & FLAG_SHORT)
476 *va_arg(argptr, short*) = written_all;
477 else
478 *va_arg(argptr, int*) = written_all;
479 continue;
480
481 case _T('C'):
482 #ifndef _UNICODE
483 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
484 #endif
485 goto case_char;
486
487 case _T('c'):
488 #ifdef _UNICODE
489 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
490 #endif
491 case_char:
492 string = buffer;
493 len = 1;
494 if (flags & FLAG_WIDECHAR)
495 {
496 ((wchar_t*)string)[0] = va_arg(argptr, int);
497 ((wchar_t*)string)[1] = _T('\0');
498 }
499 else
500 {
501 ((char*)string)[0] = va_arg(argptr, int);
502 ((char*)string)[1] = _T('\0');
503 }
504 break;
505
506 case _T('Z'):
507 nt_string = va_arg(argptr, void*);
508 if (nt_string && (string = nt_string->Buffer))
509 {
510 len = nt_string->Length;
511 if (flags & FLAG_WIDECHAR) len /= sizeof(wchar_t);
512 break;
513 }
514 string = 0;
515 goto case_string;
516
517 case _T('S'):
518 string = va_arg(argptr, void*);
519 #ifndef _UNICODE
520 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
521 #endif
522 goto case_string;
523
524 case _T('s'):
525 string = va_arg(argptr, void*);
526 #ifdef _UNICODE
527 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
528 #endif
529
530 case_string:
531 if (!string)
532 {
533 string = (TCHAR*)_nullstring;
534 flags &= ~FLAG_WIDECHAR;
535 }
536
537 if (flags & FLAG_WIDECHAR)
538 len = wcsnlen((wchar_t*)string, (unsigned)precision);
539 else
540 len = strnlen((char*)string, (unsigned)precision);
541 precision = 0;
542 break;
543
544 #ifndef _USER32_WSPRINTF
545 case _T('G'):
546 case _T('E'):
547 case _T('A'):
548 case _T('g'):
549 case _T('e'):
550 case _T('a'):
551 case _T('f'):
552 #ifdef _UNICODE
553 flags |= FLAG_WIDECHAR;
554 #else
555 flags &= ~FLAG_WIDECHAR;
556 #endif
557 /* Use external function, one for kernel one for user mode */
558 format_float(chr, flags, precision, &string, &prefix, &argptr);
559 len = _tcslen(string);
560 precision = 0;
561 break;
562 #endif
563
564 case _T('d'):
565 case _T('i'):
566 val64 = (__int64)va_arg_f(argptr, flags);
567
568 if ((__int64)val64 < 0)
569 {
570 val64 = -(__int64)val64;
571 prefix = _T("-");
572 }
573 else if (flags & FLAG_FORCE_SIGN)
574 prefix = _T("+");
575 else if (flags & FLAG_FORCE_SIGNSP)
576 prefix = _T(" ");
577
578 goto case_number;
579
580 case _T('o'):
581 base = 8;
582 if (flags & FLAG_SPECIAL)
583 {
584 prefix = _T("0");
585 if (precision > 0) precision--;
586 }
587 goto case_unsigned;
588
589 case _T('p'):
590 precision = 2 * sizeof(void*);
591 flags &= ~FLAG_PAD_ZERO;
592 flags |= FLAG_INTPTR;
593 /* Fall through */
594
595 case _T('X'):
596 digits = digits_u;
597 /* Fall through */
598
599 case _T('x'):
600 base = 16;
601 if (flags & FLAG_SPECIAL)
602 {
603 prefix = &digits[16];
604 #ifdef _USER32_WSPRINTF
605 fieldwidth += 2;
606 #endif
607 }
608
609 case _T('u'):
610 case_unsigned:
611 val64 = va_arg_fu(argptr, flags);
612
613 case_number:
614 #ifdef _UNICODE
615 flags |= FLAG_WIDECHAR;
616 #else
617 flags &= ~FLAG_WIDECHAR;
618 #endif
619 if (precision < 0) precision = 1;
620
621 /* Gather digits in reverse order */
622 while (val64)
623 {
624 *--string = digits[val64 % base];
625 val64 /= base;
626 precision--;
627 }
628
629 len = _tcslen(string);
630 break;
631
632 default:
633 /* Treat anything else as a new character */
634 format--;
635 continue;
636 }
637
638 /* Calculate padding */
639 prefixlen = prefix ? _tcslen(prefix) : 0;
640 if (precision < 0) precision = 0;
641 padding = (int)(fieldwidth - len - prefixlen - precision);
642 if (padding < 0) padding = 0;
643
644 /* Optional left space padding */
645 if ((flags & (FLAG_ALIGN_LEFT | FLAG_PAD_ZERO)) == 0)
646 {
647 for (; padding > 0; padding--)
648 {
649 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
650 written_all += written;
651 }
652 }
653
654 /* Optional prefix */
655 if (prefix)
656 {
657 written = streamout_string(stream, prefix, prefixlen);
658 if (written == -1) return -1;
659 written_all += written;
660 }
661
662 /* Optional left '0' padding */
663 if ((flags & FLAG_ALIGN_LEFT) == 0) precision += padding;
664 while (precision-- > 0)
665 {
666 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
667 written_all += written;
668 }
669
670 /* Output the string */
671 if (flags & FLAG_WIDECHAR)
672 written = streamout_wstring(stream, (wchar_t*)string, len);
673 else
674 written = streamout_astring(stream, (char*)string, len);
675 if (written == -1) return -1;
676 written_all += written;
677
678 #if 0 && SUPPORT_FLOAT
679 /* Optional right '0' padding */
680 while (precision-- > 0)
681 {
682 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
683 written_all += written;
684 len++;
685 }
686 #endif
687
688 /* Optional right padding */
689 if (flags & FLAG_ALIGN_LEFT)
690 {
691 while (padding-- > 0)
692 {
693 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
694 written_all += written;
695 }
696 }
697
698 }
699
700 if (written == -1) return -1;
701
702 return written_all;
703 }
704