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