[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 fpval *= pow(10., precision);
177 val64 = (__int64)(fpval + 0.5);
178
179 while (num_digits-- > 0)
180 {
181 *--(*string) = digits[val64 % 10];
182 val64 /= 10;
183 }
184 }
185
186 *--(*string) = _T('.');
187
188 /* Digits before the decimal point */
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 unsigned __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 or double % */
300 if ((chr != _T('%')) ||
301 (chr = *format++) == _T('%'))
302 {
303 /* Write the character to the stream */
304 if ((written = streamout_char(stream, chr)) == 0) return -1;
305 written_all += written;
306 continue;
307 }
308
309 /* Handle flags */
310 flags = 0;
311 while (1)
312 {
313 if (chr == _T('-')) flags |= FLAG_ALIGN_LEFT;
314 else if (chr == _T('+')) flags |= FLAG_FORCE_SIGN;
315 else if (chr == _T(' ')) flags |= FLAG_FORCE_SIGNSP;
316 else if (chr == _T('0')) flags |= FLAG_PAD_ZERO;
317 else if (chr == _T('#')) flags |= FLAG_SPECIAL;
318 else break;
319 chr = *format++;
320 }
321
322 /* Handle field width modifier */
323 if (chr == _T('*'))
324 {
325 fieldwidth = va_arg(argptr, int);
326 if (fieldwidth < 0)
327 {
328 flags |= FLAG_ALIGN_LEFT;
329 fieldwidth = -fieldwidth;
330 }
331 chr = *format++;
332 }
333 else
334 {
335 fieldwidth = 0;
336 while (chr >= _T('0') && chr <= _T('9'))
337 {
338 fieldwidth = fieldwidth * 10 + (chr - _T('0'));
339 chr = *format++;
340 }
341 }
342
343 /* Handle precision modifier */
344 if (chr == '.')
345 {
346 chr = *format++;
347
348 if (chr == _T('*'))
349 {
350 precision = va_arg(argptr, int);
351 chr = *format++;
352 }
353 else
354 {
355 precision = 0;
356 while (chr >= _T('0') && chr <= _T('9'))
357 {
358 precision = precision * 10 + (chr - _T('0'));
359 chr = *format++;
360 }
361 }
362 }
363 else precision = -1;
364
365 /* Handle argument size prefix */
366 while (1)
367 {
368 if (chr == _T('h')) flags |= FLAG_SHORT;
369 else if (chr == _T('w')) flags |= FLAG_WIDECHAR;
370 else if (chr == _T('L')) flags |= 0; // FIXME: long double
371 else if (chr == _T('l'))
372 {
373 flags |= FLAG_LONG;
374 #if SUPPORT_LL
375 if (format[0] == _T('l'))
376 {
377 format++;
378 flags |= FLAG_INT64;
379 }
380 #endif
381 }
382 else if (chr == _T('I'))
383 {
384 if (format[0] == _T('3') && format[1] == _T('2'))
385 {
386 format += 2;
387 }
388 else if (format[0] == _T('6') && format[1] == _T('4'))
389 {
390 format += 2;
391 flags |= FLAG_INT64;
392 }
393 else if (format[0] == _T('x') || format[0] == _T('X') ||
394 format[0] == _T('d') || format[0] == _T('i') ||
395 format[0] == _T('u') || format[0] == _T('o'))
396 {
397 flags |= FLAG_INTPTR;
398 }
399 else break;
400 }
401 else break;
402 chr = *format++;
403 }
404
405 /* Handle the format specifier */
406 digits = digits_l;
407 string = &buffer[BUFFER_SIZE];
408 base = 10;
409 prefix = 0;
410 switch (chr)
411 {
412 case _T('n'):
413 if (flags & FLAG_INT64)
414 *va_arg(argptr, __int64*) = written_all;
415 else if (flags & FLAG_SHORT)
416 *va_arg(argptr, short*) = written_all;
417 else
418 *va_arg(argptr, int*) = written_all;
419 continue;
420
421 case _T('C'):
422 #ifndef _UNICODE
423 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
424 #endif
425 goto case_char;
426
427 case _T('c'):
428 #ifdef _UNICODE
429 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
430 #endif
431 case_char:
432 string = buffer;
433 len = 1;
434 if (flags & FLAG_WIDECHAR)
435 {
436 ((wchar_t*)string)[0] = va_arg(argptr, int);
437 ((wchar_t*)string)[1] = _T('\0');
438 }
439 else
440 {
441 ((char*)string)[0] = va_arg(argptr, int);
442 ((char*)string)[1] = _T('\0');
443 }
444 break;
445
446 case _T('Z'):
447 nt_string = va_arg(argptr, void*);
448 if (nt_string && (string = nt_string->Buffer))
449 {
450 len = nt_string->Length;
451 if (flags & FLAG_WIDECHAR) len /= sizeof(wchar_t);
452 break;
453 }
454 string = 0;
455 goto case_string;
456
457 case _T('S'):
458 string = va_arg(argptr, void*);
459 #ifndef _UNICODE
460 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
461 #endif
462 goto case_string;
463
464 case _T('s'):
465 string = va_arg(argptr, void*);
466 #ifdef _UNICODE
467 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
468 #endif
469
470 case_string:
471 if (!string)
472 {
473 string = (TCHAR*)_nullstring;
474 flags &= ~FLAG_WIDECHAR;
475 }
476
477 if (flags & FLAG_WIDECHAR)
478 len = wcslen((wchar_t*)string);
479 else
480 len = strlen((char*)string);
481 if (precision >= 0 && len > precision) len = precision;
482 break;
483
484 case _T('G'):
485 case _T('E'):
486 case _T('A'):
487 case _T('g'):
488 case _T('e'):
489 case _T('a'):
490 case _T('f'):
491 #ifdef _UNICODE
492 flags |= FLAG_WIDECHAR;
493 #else
494 flags &= ~FLAG_WIDECHAR;
495 #endif
496 /* Use external function, one for kernel one for user mode */
497 format_float(chr, flags, precision, &string, &prefix, &argptr);
498 len = _tcslen(string);
499 precision = 0;
500 break;
501
502 case _T('d'):
503 case _T('i'):
504 val64 = (__int64)va_arg_f(argptr, flags);
505
506 if ((__int64)val64 < 0)
507 {
508 val64 = -val64;
509 prefix = _T("-");
510 }
511 else if (flags & FLAG_FORCE_SIGN)
512 prefix = _T("+");
513 else if (flags & FLAG_FORCE_SIGNSP)
514 prefix = _T(" ");
515
516 goto case_number;
517
518 case _T('o'):
519 base = 8;
520 if (flags & FLAG_SPECIAL) prefix = _T("0");
521 goto case_unsigned;
522 /* Fall through */
523
524 case _T('p'):
525 precision = 2 * sizeof(void*);
526 flags &= ~FLAG_PAD_ZERO;
527 flags |= FLAG_INTPTR;
528 /* Fall through */
529
530 case _T('X'):
531 digits = digits_u;
532 /* Fall through */
533
534 case _T('x'):
535 base = 16;
536 if (flags & FLAG_SPECIAL)
537 {
538 prefix = &digits[16];
539 }
540
541 case _T('u'):
542 case_unsigned:
543 val64 = va_arg_fu(argptr, flags);
544
545 case_number:
546 #ifdef _UNICODE
547 flags |= FLAG_WIDECHAR;
548 #else
549 flags &= ~FLAG_WIDECHAR;
550 #endif
551 if (precision < 0) precision = 1;
552
553 /* Gather digits in reverse order */
554 while (val64)
555 {
556 *--string = digits[val64 % base];
557 val64 /= base;
558 precision--;
559 }
560
561 len = _tcslen(string);
562 break;
563
564 default:
565 /* Treat anything else as a new character */
566 format--;
567 continue;
568 }
569
570 /* Calculate padding */
571 prefixlen = prefix ? _tcslen(prefix) : 0;
572 if (precision < 0) precision = 0;
573 padding = fieldwidth - len - prefixlen - precision;
574 if (padding < 0) padding = 0;
575
576 /* Optional left space padding */
577 if ((flags & (FLAG_ALIGN_LEFT | FLAG_PAD_ZERO)) == 0)
578 {
579 for (; padding > 0; padding--)
580 {
581 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
582 written_all += written;
583 }
584 }
585
586 /* Optional prefix */
587 if (prefix)
588 {
589 written = streamout_string(stream, prefix, prefixlen);
590 if (written == -1) return -3;
591 written_all += written;
592 }
593
594 /* Optional left '0' padding */
595 if ((flags & FLAG_ALIGN_LEFT) == 0) precision += padding;
596 while (precision-- > 0)
597 {
598 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
599 written_all += written;
600 }
601
602 /* Output the string */
603 if (flags & FLAG_WIDECHAR)
604 written = streamout_wstring(stream, (wchar_t*)string, len);
605 else
606 written = streamout_astring(stream, (char*)string, len);
607 if (written == -1) return -5;
608 written_all += written;
609
610 #if 0 && SUPPORT_FLOAT
611 /* Optional right '0' padding */
612 while (precision-- > 0)
613 {
614 if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
615 written_all += written;
616 len++;
617 }
618 #endif
619
620 /* Optional right padding */
621 if (flags & FLAG_ALIGN_LEFT)
622 {
623 while (padding-- > 0)
624 {
625 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
626 written_all += written;
627 }
628 }
629
630 }
631
632 if (written == -1) return -8;
633
634 return written_all;
635 }
636