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