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