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