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