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