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