f427c84f286e09243a95fb37c5fbc0a9e146161f
[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 #if !defined(_USER32_WSPRINTF)
231 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
232 return 1;
233 #endif
234 #if defined(_USER32_WSPRINTF) || defined(_LIBCNT_)
235 /* Check if the buffer is full */
236 if (stream->_cnt < sizeof(TCHAR))
237 return 0;
238
239 *(TCHAR*)stream->_ptr = chr;
240 stream->_ptr += sizeof(TCHAR);
241 stream->_cnt -= sizeof(TCHAR);
242
243 return 1;
244 #else
245 return _fputtc((TCHAR)chr, stream) != _TEOF;
246 #endif
247 }
248
249 static
250 int
251 streamout_astring(FILE *stream, const char *string, size_t count)
252 {
253 TCHAR chr;
254 int written = 0;
255
256 #if !defined(_USER32_WSPRINTF)
257 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
258 return count;
259 #endif
260
261 while (count--)
262 {
263 #ifdef _UNICODE
264 int len;
265 if ((len = mbtowc(&chr, string, MB_CUR_MAX)) < 1) break;
266 string += len;
267 #else
268 chr = *string++;
269 #endif
270 if (streamout_char(stream, chr) == 0) return -1;
271 written++;
272 }
273
274 return written;
275 }
276
277 static
278 int
279 streamout_wstring(FILE *stream, const wchar_t *string, size_t count)
280 {
281 wchar_t chr;
282 int written = 0;
283
284 #if defined(_UNICODE) && !defined(_USER32_WSPRINTF)
285 if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
286 return count;
287 #endif
288
289 while (count--)
290 {
291 #ifndef _UNICODE
292 char mbchar[MB_CUR_MAX], *ptr = mbchar;
293 int mblen;
294
295 mblen = wctomb(mbchar, *string++);
296 if (mblen <= 0) return written;
297
298 while (chr = *ptr++, mblen--)
299 #else
300 chr = *string++;
301 #endif
302 {
303 if (streamout_char(stream, chr) == 0) return -1;
304 written++;
305 }
306 }
307
308 return written;
309 }
310
311 #ifdef _UNICODE
312 #define streamout_string streamout_wstring
313 #else
314 #define streamout_string streamout_astring
315 #endif
316
317 #ifdef _USER32_WSPRINTF
318 # define USE_MULTISIZE 0
319 #else
320 # define USE_MULTISIZE 1
321 #endif
322
323 int
324 __cdecl
325 streamout(FILE *stream, const TCHAR *format, va_list argptr)
326 {
327 static const TCHAR digits_l[] = _T("0123456789abcdef0x");
328 static const TCHAR digits_u[] = _T("0123456789ABCDEF0X");
329 static const char *_nullstring = "(null)";
330 TCHAR buffer[BUFFER_SIZE + 1];
331 TCHAR chr, *string;
332 STRING *nt_string;
333 const TCHAR *digits, *prefix;
334 int base, fieldwidth, precision, padding;
335 size_t prefixlen, len;
336 int written = 1, written_all = 0;
337 unsigned int flags;
338 unsigned __int64 val64;
339
340 buffer[BUFFER_SIZE] = '\0';
341
342 while (written >= 0)
343 {
344 chr = *format++;
345
346 /* Check for end of format string */
347 if (chr == _T('\0')) break;
348
349 /* Check for 'normal' character or double % */
350 if ((chr != _T('%')) ||
351 (chr = *format++) == _T('%'))
352 {
353 /* Write the character to the stream */
354 if ((written = streamout_char(stream, chr)) == 0) return -1;
355 written_all += written;
356 continue;
357 }
358
359 /* Handle flags */
360 flags = 0;
361 while (1)
362 {
363 if (chr == _T('-')) flags |= FLAG_ALIGN_LEFT;
364 else if (chr == _T('+')) flags |= FLAG_FORCE_SIGN;
365 else if (chr == _T(' ')) flags |= FLAG_FORCE_SIGNSP;
366 else if (chr == _T('0')) flags |= FLAG_PAD_ZERO;
367 else if (chr == _T('#')) flags |= FLAG_SPECIAL;
368 else break;
369 chr = *format++;
370 }
371
372 /* Handle field width modifier */
373 if (chr == _T('*'))
374 {
375 fieldwidth = va_arg(argptr, int);
376 if (fieldwidth < 0)
377 {
378 flags |= FLAG_ALIGN_LEFT;
379 fieldwidth = -fieldwidth;
380 }
381 chr = *format++;
382 }
383 else
384 {
385 fieldwidth = 0;
386 while (chr >= _T('0') && chr <= _T('9'))
387 {
388 fieldwidth = fieldwidth * 10 + (chr - _T('0'));
389 chr = *format++;
390 }
391 }
392
393 /* Handle precision modifier */
394 if (chr == '.')
395 {
396 chr = *format++;
397
398 if (chr == _T('*'))
399 {
400 precision = va_arg(argptr, int);
401 chr = *format++;
402 }
403 else
404 {
405 precision = 0;
406 while (chr >= _T('0') && chr <= _T('9'))
407 {
408 precision = precision * 10 + (chr - _T('0'));
409 chr = *format++;
410 }
411 }
412 }
413 else precision = -1;
414
415 /* Handle argument size prefix */
416 do
417 {
418 if (chr == _T('h')) flags |= FLAG_SHORT;
419 else if (chr == _T('w')) flags |= FLAG_WIDECHAR;
420 else if (chr == _T('L')) flags |= 0; // FIXME: long double
421 else if (chr == _T('F')) flags |= 0; // FIXME: what is that?
422 else if (chr == _T('l'))
423 {
424 /* Check if this is the 2nd 'l' in a row */
425 if (format[-2] == 'l') flags |= FLAG_INT64;
426 else flags |= FLAG_LONG;
427 }
428 else if (chr == _T('I'))
429 {
430 if (format[0] == _T('3') && format[1] == _T('2'))
431 {
432 format += 2;
433 }
434 else if (format[0] == _T('6') && format[1] == _T('4'))
435 {
436 format += 2;
437 flags |= FLAG_INT64;
438 }
439 else if (format[0] == _T('x') || format[0] == _T('X') ||
440 format[0] == _T('d') || format[0] == _T('i') ||
441 format[0] == _T('u') || format[0] == _T('o'))
442 {
443 flags |= FLAG_INTPTR;
444 }
445 else break;
446 }
447 else break;
448 chr = *format++;
449 }
450 while (USE_MULTISIZE);
451
452 /* Handle the format specifier */
453 digits = digits_l;
454 string = &buffer[BUFFER_SIZE];
455 base = 10;
456 prefix = 0;
457 switch (chr)
458 {
459 case _T('n'):
460 if (flags & FLAG_INT64)
461 *va_arg(argptr, __int64*) = written_all;
462 else if (flags & FLAG_SHORT)
463 *va_arg(argptr, short*) = written_all;
464 else
465 *va_arg(argptr, int*) = written_all;
466 continue;
467
468 case _T('C'):
469 #ifndef _UNICODE
470 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
471 #endif
472 goto case_char;
473
474 case _T('c'):
475 #ifdef _UNICODE
476 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
477 #endif
478 case_char:
479 string = buffer;
480 len = 1;
481 if (flags & FLAG_WIDECHAR)
482 {
483 ((wchar_t*)string)[0] = va_arg(argptr, int);
484 ((wchar_t*)string)[1] = _T('\0');
485 }
486 else
487 {
488 ((char*)string)[0] = va_arg(argptr, int);
489 ((char*)string)[1] = _T('\0');
490 }
491 break;
492
493 case _T('Z'):
494 nt_string = va_arg(argptr, void*);
495 if (nt_string && (string = nt_string->Buffer))
496 {
497 len = nt_string->Length;
498 if (flags & FLAG_WIDECHAR) len /= sizeof(wchar_t);
499 break;
500 }
501 string = 0;
502 goto case_string;
503
504 case _T('S'):
505 string = va_arg(argptr, void*);
506 #ifndef _UNICODE
507 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
508 #endif
509 goto case_string;
510
511 case _T('s'):
512 string = va_arg(argptr, void*);
513 #ifdef _UNICODE
514 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
515 #endif
516
517 case_string:
518 if (!string)
519 {
520 string = (TCHAR*)_nullstring;
521 flags &= ~FLAG_WIDECHAR;
522 }
523
524 if (flags & FLAG_WIDECHAR)
525 len = wcsnlen((wchar_t*)string, (unsigned)precision);
526 else
527 len = strnlen((char*)string, (unsigned)precision);
528 precision = 0;
529 break;
530
531 #ifndef _USER32_WSPRINTF
532 case _T('G'):
533 case _T('E'):
534 case _T('A'):
535 case _T('g'):
536 case _T('e'):
537 case _T('a'):
538 case _T('f'):
539 #ifdef _UNICODE
540 flags |= FLAG_WIDECHAR;
541 #else
542 flags &= ~FLAG_WIDECHAR;
543 #endif
544 /* Use external function, one for kernel one for user mode */
545 format_float(chr, flags, precision, &string, &prefix, &argptr);
546 len = _tcslen(string);
547 precision = 0;
548 break;
549 #endif
550
551 case _T('d'):
552 case _T('i'):
553 val64 = (__int64)va_arg_f(argptr, flags);
554
555 if ((__int64)val64 < 0)
556 {
557 val64 = -(__int64)val64;
558 prefix = _T("-");
559 }
560 else if (flags & FLAG_FORCE_SIGN)
561 prefix = _T("+");
562 else if (flags & FLAG_FORCE_SIGNSP)
563 prefix = _T(" ");
564
565 goto case_number;
566
567 case _T('o'):
568 base = 8;
569 if (flags & FLAG_SPECIAL)
570 {
571 prefix = _T("0");
572 if (precision > 0) precision--;
573 }
574 goto case_unsigned;
575
576 case _T('p'):
577 precision = 2 * sizeof(void*);
578 flags &= ~FLAG_PAD_ZERO;
579 flags |= FLAG_INTPTR;
580 /* Fall through */
581
582 case _T('X'):
583 digits = digits_u;
584 /* Fall through */
585
586 case _T('x'):
587 base = 16;
588 if (flags & FLAG_SPECIAL)
589 {
590 prefix = &digits[16];
591 #ifdef _USER32_WSPRINTF
592 fieldwidth += 2;
593 #endif
594 }
595
596 case _T('u'):
597 case_unsigned:
598 val64 = va_arg_fu(argptr, flags);
599
600 case_number:
601 #ifdef _UNICODE
602 flags |= FLAG_WIDECHAR;
603 #else
604 flags &= ~FLAG_WIDECHAR;
605 #endif
606 if (precision < 0) precision = 1;
607
608 /* Gather digits in reverse order */
609 while (val64)
610 {
611 *--string = digits[val64 % base];
612 val64 /= base;
613 precision--;
614 }
615
616 len = _tcslen(string);
617 break;
618
619 default:
620 /* Treat anything else as a new character */
621 format--;
622 continue;
623 }
624
625 /* Calculate padding */
626 prefixlen = prefix ? _tcslen(prefix) : 0;
627 if (precision < 0) precision = 0;
628 padding = (int)(fieldwidth - len - prefixlen - precision);
629 if (padding < 0) padding = 0;
630
631 /* Optional left space padding */
632 if ((flags & (FLAG_ALIGN_LEFT | FLAG_PAD_ZERO)) == 0)
633 {
634 for (; padding > 0; padding--)
635 {
636 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
637 written_all += written;
638 }
639 }
640
641 /* Optional prefix */
642 if (prefix)
643 {
644 written = streamout_string(stream, prefix, prefixlen);
645 if (written == -1) return -1;
646 written_all += written;
647 }
648
649 /* Optional left '0' padding */
650 if ((flags & FLAG_ALIGN_LEFT) == 0) precision += padding;
651 while (precision-- > 0)
652 {
653 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
654 written_all += written;
655 }
656
657 /* Output the string */
658 if (flags & FLAG_WIDECHAR)
659 written = streamout_wstring(stream, (wchar_t*)string, len);
660 else
661 written = streamout_astring(stream, (char*)string, len);
662 if (written == -1) return -1;
663 written_all += written;
664
665 #if 0 && SUPPORT_FLOAT
666 /* Optional right '0' padding */
667 while (precision-- > 0)
668 {
669 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
670 written_all += written;
671 len++;
672 }
673 #endif
674
675 /* Optional right padding */
676 if (flags & FLAG_ALIGN_LEFT)
677 {
678 while (padding-- > 0)
679 {
680 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
681 written_all += written;
682 }
683 }
684
685 }
686
687 if (written == -1) return -1;
688
689 return written_all;
690 }
691