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