xref: /reactos/sdk/lib/crt/printf/streamout.c (revision 23373acb)
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  * PROGRAMMERS:     Timo Kreuzer
7  *                  Katayama Hirofumi MZ
8  */
9 
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <tchar.h>
13 #include <strings.h>
14 #include <math.h>
15 #include <float.h>
16 
17 #ifdef _UNICODE
18 # define streamout wstreamout
19 # define format_float format_floatw
20 #endif
21 
22 #define MB_CUR_MAX 10
23 #define BUFFER_SIZE (32 + 17)
24 
25 int mbtowc(wchar_t *wchar, const char *mbchar, size_t count);
26 int wctomb(char *mbchar, wchar_t wchar);
27 
28 typedef struct _STRING
29 {
30   unsigned short Length;
31   unsigned short MaximumLength;
32   void *Buffer;
33 } STRING;
34 
35 enum
36 {
37     /* Formatting flags */
38     FLAG_ALIGN_LEFT =    0x01,
39     FLAG_FORCE_SIGN =    0x02,
40     FLAG_FORCE_SIGNSP =  0x04,
41     FLAG_PAD_ZERO =      0x08,
42     FLAG_SPECIAL =       0x10,
43 
44     /* Data format flags */
45     FLAG_SHORT =         0x100,
46     FLAG_LONG =          0x200,
47     FLAG_WIDECHAR =      FLAG_LONG,
48     FLAG_INT64 =         0x400,
49 #ifdef _WIN64
50     FLAG_INTPTR =        FLAG_INT64,
51 #else
52     FLAG_INTPTR =        0,
53 #endif
54     FLAG_LONGDOUBLE =    0x800,
55 };
56 
57 #define va_arg_f(argptr, flags) \
58     (flags & FLAG_INT64) ? va_arg(argptr, __int64) : \
59     (flags & FLAG_SHORT) ? (short)va_arg(argptr, int) : \
60     va_arg(argptr, int)
61 
62 #define va_arg_fu(argptr, flags) \
63     (flags & FLAG_INT64) ? va_arg(argptr, unsigned __int64) : \
64     (flags & FLAG_SHORT) ? (unsigned short)va_arg(argptr, int) : \
65     va_arg(argptr, unsigned int)
66 
67 #define va_arg_ffp(argptr, flags) \
68     (flags & FLAG_LONGDOUBLE) ? va_arg(argptr, long double) : \
69     va_arg(argptr, double)
70 
71 #define get_exp(f) (int)floor(f == 0 ? 0 : (f >= 0 ? log10(f) : log10(-f)))
72 #define round(x) floor((x) + 0.5)
73 
74 #ifndef _USER32_WSPRINTF
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, sign;
96     long double fpval, fpval2;
97     int padding = 0, num_digits, val32, base = 10;
98 
99     /* Normalize the precision */
100     if (precision < 0) precision = 6;
101     else if (precision > 17)
102     {
103         padding = precision - 17;
104         precision = 17;
105     }
106 
107     /* Get the float value and calculate the exponent */
108     fpval = va_arg_ffp(*argptr, flags);
109     exponent = get_exp(fpval);
110     sign = fpval < 0 ? -1 : 1;
111 
112     switch (chr)
113     {
114         case _T('G'):
115             digits = digits_u;
116         case _T('g'):
117             if (precision > 0) precision--;
118             if (exponent < -4 || exponent >= precision) goto case_e;
119 
120             /* Shift the decimal point and round */
121             fpval2 = round(sign * fpval * pow(10., precision));
122 
123             /* Skip trailing zeroes */
124             while (precision && (unsigned __int64)fpval2 % 10 == 0)
125             {
126                 precision--;
127                 fpval2 /= 10;
128             }
129             break;
130 
131         case _T('E'):
132             digits = digits_u;
133         case _T('e'):
134         case_e:
135             /* Shift the decimal point and round */
136             fpval2 = round(sign * fpval * pow(10., precision - exponent));
137 
138             /* Compensate for changed exponent through rounding */
139             if (fpval2 >= (unsigned __int64)pow(10., precision + 1))
140             {
141                 exponent++;
142                 fpval2 = round(sign * fpval * pow(10., precision - exponent));
143             }
144 
145             val32 = exponent >= 0 ? exponent : -exponent;
146 
147             // FIXME: handle length of exponent field:
148             // http://msdn.microsoft.com/de-de/library/0fatw238%28VS.80%29.aspx
149             num_digits = 3;
150             while (num_digits--)
151             {
152                 *--(*string) = digits[val32 % 10];
153                 val32 /= 10;
154             }
155 
156             /* Sign for the exponent */
157             *--(*string) = exponent >= 0 ? _T('+') : _T('-');
158 
159             /* Add 'e' or 'E' separator */
160             *--(*string) = digits[0xe];
161             break;
162 
163         case _T('A'):
164             digits = digits_u;
165         case _T('a'):
166 //            base = 16;
167             // FIXME: TODO
168 
169         case _T('f'):
170         default:
171             /* Shift the decimal point and round */
172             fpval2 = round(sign * fpval * pow(10., precision));
173             break;
174     }
175 
176     /* Handle sign */
177     if (fpval < 0)
178     {
179         *prefix = _T("-");
180     }
181     else if (flags & FLAG_FORCE_SIGN)
182         *prefix = _T("+");
183     else if (flags & FLAG_FORCE_SIGNSP)
184         *prefix = _T(" ");
185 
186     /* Handle special cases first */
187     if (_isnan(fpval))
188     {
189         (*string) -= sizeof(_nan) / sizeof(TCHAR) - 1;
190         _tcscpy((*string), _nan);
191         fpval2 = 1;
192     }
193     else if (!_finite(fpval))
194     {
195         (*string) -= sizeof(_infinity) / sizeof(TCHAR) - 1;
196         _tcscpy((*string), _infinity);
197         fpval2 = 1;
198     }
199     else
200     {
201         /* Zero padding */
202         while (padding-- > 0) *--(*string) = _T('0');
203 
204         /* Digits after the decimal point */
205         num_digits = precision;
206         while (num_digits-- > 0)
207         {
208             *--(*string) = digits[(unsigned __int64)fpval2 % 10];
209             fpval2 /= base;
210         }
211     }
212 
213     if (precision > 0 || flags & FLAG_SPECIAL)
214         *--(*string) = _T('.');
215 
216     /* Digits before the decimal point */
217     do
218     {
219         *--(*string) = digits[(unsigned __int64)fpval2 % base];
220         fpval2 /= base;
221     }
222     while ((unsigned __int64)fpval2);
223 
224 }
225 #endif
226 
227 static
228 int
229 streamout_char(FILE *stream, int chr)
230 {
231 #if !defined(_USER32_WSPRINTF)
232      if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
233         return 1;
234 #endif
235 #if defined(_USER32_WSPRINTF) || defined(_LIBCNT_)
236     /* Check if the buffer is full */
237     if (stream->_cnt < sizeof(TCHAR))
238         return 0;
239 
240     *(TCHAR*)stream->_ptr = chr;
241     stream->_ptr += sizeof(TCHAR);
242     stream->_cnt -= sizeof(TCHAR);
243 
244     return 1;
245 #else
246     return _fputtc((TCHAR)chr, stream) != _TEOF;
247 #endif
248 }
249 
250 static
251 int
252 streamout_astring(FILE *stream, const char *string, size_t count)
253 {
254     TCHAR chr;
255     int written = 0;
256 
257 #if !defined(_USER32_WSPRINTF)
258      if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
259         return count;
260 #endif
261 
262     while (count--)
263     {
264 #ifdef _UNICODE
265         int len;
266         if ((len = mbtowc(&chr, string, MB_CUR_MAX)) < 1) break;
267         string += len;
268 #else
269         chr = *string++;
270 #endif
271         if (streamout_char(stream, chr) == 0) return -1;
272         written++;
273     }
274 
275     return written;
276 }
277 
278 static
279 int
280 streamout_wstring(FILE *stream, const wchar_t *string, size_t count)
281 {
282     wchar_t chr;
283     int written = 0;
284 
285 #if defined(_UNICODE) && !defined(_USER32_WSPRINTF)
286      if ((stream->_flag & _IOSTRG) && (stream->_base == NULL))
287         return count;
288 #endif
289 
290     while (count--)
291     {
292 #ifndef _UNICODE
293         char mbchar[MB_CUR_MAX], *ptr = mbchar;
294         int mblen;
295 
296         mblen = wctomb(mbchar, *string++);
297         if (mblen <= 0) return written;
298 
299         while (chr = *ptr++, mblen--)
300 #else
301         chr = *string++;
302 #endif
303         {
304             if (streamout_char(stream, chr) == 0) return -1;
305             written++;
306         }
307     }
308 
309     return written;
310 }
311 
312 #ifdef _UNICODE
313 #define streamout_string streamout_wstring
314 #else
315 #define streamout_string streamout_astring
316 #endif
317 
318 #ifdef _USER32_WSPRINTF
319 # define USE_MULTISIZE 0
320 #else
321 # define USE_MULTISIZE 1
322 #endif
323 
324 int
325 __cdecl
326 streamout(FILE *stream, const TCHAR *format, va_list argptr)
327 {
328     static const TCHAR digits_l[] = _T("0123456789abcdef0x");
329     static const TCHAR digits_u[] = _T("0123456789ABCDEF0X");
330     static const char *_nullstring = "(null)";
331     TCHAR buffer[BUFFER_SIZE + 1];
332     TCHAR chr, *string;
333     STRING *nt_string;
334     const TCHAR *digits, *prefix;
335     int base, fieldwidth, precision, padding;
336     size_t prefixlen, len;
337     int written = 1, written_all = 0;
338     unsigned int flags;
339     unsigned __int64 val64;
340 
341     buffer[BUFFER_SIZE] = '\0';
342 
343     while (written >= 0)
344     {
345         chr = *format++;
346 
347         /* Check for end of format string */
348         if (chr == _T('\0')) break;
349 
350         /* Check for 'normal' character or double % */
351         if ((chr != _T('%')) ||
352             (chr = *format++) == _T('%'))
353         {
354             /* Write the character to the stream */
355             if ((written = streamout_char(stream, chr)) == 0) return -1;
356             written_all += written;
357             continue;
358         }
359 
360         /* Handle flags */
361         flags = 0;
362         while (1)
363         {
364                  if (chr == _T('-')) flags |= FLAG_ALIGN_LEFT;
365             else if (chr == _T('+')) flags |= FLAG_FORCE_SIGN;
366             else if (chr == _T(' ')) flags |= FLAG_FORCE_SIGNSP;
367             else if (chr == _T('0')) flags |= FLAG_PAD_ZERO;
368             else if (chr == _T('#')) flags |= FLAG_SPECIAL;
369             else break;
370             chr = *format++;
371         }
372 
373         /* Handle field width modifier */
374         if (chr == _T('*'))
375         {
376 #ifdef _USER32_WSPRINTF
377             if ((written = streamout_char(stream, chr)) == 0) return -1;
378             written_all += written;
379             continue;
380 #else
381             fieldwidth = va_arg(argptr, int);
382             if (fieldwidth < 0)
383             {
384                 flags |= FLAG_ALIGN_LEFT;
385                 fieldwidth = -fieldwidth;
386             }
387             chr = *format++;
388 #endif
389         }
390         else
391         {
392             fieldwidth = 0;
393             while (chr >= _T('0') && chr <= _T('9'))
394             {
395                 fieldwidth = fieldwidth * 10 + (chr - _T('0'));
396                 chr = *format++;
397             }
398         }
399 
400         /* Handle precision modifier */
401         if (chr == '.')
402         {
403             chr = *format++;
404 
405             if (chr == _T('*'))
406             {
407 #ifdef _USER32_WSPRINTF
408                 if ((written = streamout_char(stream, chr)) == 0) return -1;
409                 written_all += written;
410                 continue;
411 #else
412                 precision = va_arg(argptr, int);
413                 chr = *format++;
414 #endif
415             }
416             else
417             {
418                 precision = 0;
419                 while (chr >= _T('0') && chr <= _T('9'))
420                 {
421                     precision = precision * 10 + (chr - _T('0'));
422                     chr = *format++;
423                 }
424             }
425         }
426         else precision = -1;
427 
428         /* Handle argument size prefix */
429         do
430         {
431                  if (chr == _T('h')) flags |= FLAG_SHORT;
432             else if (chr == _T('w')) flags |= FLAG_WIDECHAR;
433             else if (chr == _T('L')) flags |= 0; // FIXME: long double
434             else if (chr == _T('F')) flags |= 0; // FIXME: what is that?
435             else if (chr == _T('l'))
436             {
437                 /* Check if this is the 2nd 'l' in a row */
438                 if (format[-2] == 'l') flags |= FLAG_INT64;
439                 else flags |= FLAG_LONG;
440             }
441             else if (chr == _T('I'))
442             {
443                 if (format[0] == _T('3') && format[1] == _T('2'))
444                 {
445                     format += 2;
446                 }
447                 else if (format[0] == _T('6') && format[1] == _T('4'))
448                 {
449                     format += 2;
450                     flags |= FLAG_INT64;
451                 }
452                 else if (format[0] == _T('x') || format[0] == _T('X') ||
453                          format[0] == _T('d') || format[0] == _T('i') ||
454                          format[0] == _T('u') || format[0] == _T('o'))
455                 {
456                     flags |= FLAG_INTPTR;
457                 }
458                 else break;
459             }
460             else break;
461             chr = *format++;
462         }
463         while (USE_MULTISIZE);
464 
465         /* Handle the format specifier */
466         digits = digits_l;
467         string = &buffer[BUFFER_SIZE];
468         base = 10;
469         prefix = 0;
470         switch (chr)
471         {
472             case _T('n'):
473                 if (flags & FLAG_INT64)
474                     *va_arg(argptr, __int64*) = written_all;
475                 else if (flags & FLAG_SHORT)
476                     *va_arg(argptr, short*) = written_all;
477                 else
478                     *va_arg(argptr, int*) = written_all;
479                 continue;
480 
481             case _T('C'):
482 #ifndef _UNICODE
483                 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
484 #endif
485                 goto case_char;
486 
487             case _T('c'):
488 #ifdef _UNICODE
489                 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
490 #endif
491             case_char:
492                 string = buffer;
493                 len = 1;
494                 if (flags & FLAG_WIDECHAR)
495                 {
496                     ((wchar_t*)string)[0] = va_arg(argptr, int);
497                     ((wchar_t*)string)[1] = _T('\0');
498                 }
499                 else
500                 {
501                     ((char*)string)[0] = va_arg(argptr, int);
502                     ((char*)string)[1] = _T('\0');
503                 }
504                 break;
505 
506             case _T('Z'):
507                 nt_string = va_arg(argptr, void*);
508                 if (nt_string && (string = nt_string->Buffer))
509                 {
510                     len = nt_string->Length;
511                     if (flags & FLAG_WIDECHAR) len /= sizeof(wchar_t);
512                     break;
513                 }
514                 string = 0;
515                 goto case_string;
516 
517             case _T('S'):
518                 string = va_arg(argptr, void*);
519 #ifndef _UNICODE
520                 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
521 #endif
522                 goto case_string;
523 
524             case _T('s'):
525                 string = va_arg(argptr, void*);
526 #ifdef _UNICODE
527                 if (!(flags & FLAG_SHORT)) flags |= FLAG_WIDECHAR;
528 #endif
529 
530             case_string:
531                 if (!string)
532                 {
533                     string = (TCHAR*)_nullstring;
534                     flags &= ~FLAG_WIDECHAR;
535                 }
536 
537                 if (flags & FLAG_WIDECHAR)
538                     len = wcsnlen((wchar_t*)string, (unsigned)precision);
539                 else
540                     len = strnlen((char*)string, (unsigned)precision);
541                 precision = 0;
542                 break;
543 
544 #ifndef _USER32_WSPRINTF
545             case _T('G'):
546             case _T('E'):
547             case _T('A'):
548             case _T('g'):
549             case _T('e'):
550             case _T('a'):
551             case _T('f'):
552 #ifdef _UNICODE
553                 flags |= FLAG_WIDECHAR;
554 #else
555                 flags &= ~FLAG_WIDECHAR;
556 #endif
557                 /* Use external function, one for kernel one for user mode */
558                 format_float(chr, flags, precision, &string, &prefix, &argptr);
559                 len = _tcslen(string);
560                 precision = 0;
561                 break;
562 #endif
563 
564             case _T('d'):
565             case _T('i'):
566                 val64 = (__int64)va_arg_f(argptr, flags);
567 
568                 if ((__int64)val64 < 0)
569                 {
570                     val64 = -(__int64)val64;
571                     prefix = _T("-");
572                 }
573                 else if (flags & FLAG_FORCE_SIGN)
574                     prefix = _T("+");
575                 else if (flags & FLAG_FORCE_SIGNSP)
576                     prefix = _T(" ");
577 
578                 goto case_number;
579 
580             case _T('o'):
581                 base = 8;
582                 if (flags & FLAG_SPECIAL)
583                 {
584                     prefix = _T("0");
585                     if (precision > 0) precision--;
586                 }
587                 goto case_unsigned;
588 
589             case _T('p'):
590                 precision = 2 * sizeof(void*);
591                 flags &= ~FLAG_PAD_ZERO;
592                 flags |= FLAG_INTPTR;
593                 /* Fall through */
594 
595             case _T('X'):
596                 digits = digits_u;
597                 /* Fall through */
598 
599             case _T('x'):
600                 base = 16;
601                 if (flags & FLAG_SPECIAL)
602                 {
603                     prefix = &digits[16];
604 #ifdef _USER32_WSPRINTF
605                     fieldwidth += 2;
606 #endif
607                 }
608 
609             case _T('u'):
610             case_unsigned:
611                 val64 = va_arg_fu(argptr, flags);
612 
613             case_number:
614 #ifdef _UNICODE
615                 flags |= FLAG_WIDECHAR;
616 #else
617                 flags &= ~FLAG_WIDECHAR;
618 #endif
619                 if (precision < 0) precision = 1;
620 
621                 /* Gather digits in reverse order */
622                 while (val64)
623                 {
624                     *--string = digits[val64 % base];
625                     val64 /= base;
626                     precision--;
627                 }
628 
629                 len = _tcslen(string);
630                 break;
631 
632             default:
633                 /* Treat anything else as a new character */
634                 format--;
635                 continue;
636         }
637 
638         /* Calculate padding */
639         prefixlen = prefix ? _tcslen(prefix) : 0;
640         if (precision < 0) precision = 0;
641         padding = (int)(fieldwidth - len - prefixlen - precision);
642         if (padding < 0) padding = 0;
643 
644         /* Optional left space padding */
645         if ((flags & (FLAG_ALIGN_LEFT | FLAG_PAD_ZERO)) == 0)
646         {
647             for (; padding > 0; padding--)
648             {
649                 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
650                 written_all += written;
651             }
652         }
653 
654         /* Optional prefix */
655         if (prefix)
656         {
657             written = streamout_string(stream, prefix, prefixlen);
658             if (written == -1) return -1;
659             written_all += written;
660         }
661 
662         /* Optional left '0' padding */
663         if ((flags & FLAG_ALIGN_LEFT) == 0) precision += padding;
664         while (precision-- > 0)
665         {
666             if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
667             written_all += written;
668         }
669 
670         /* Output the string */
671         if (flags & FLAG_WIDECHAR)
672             written = streamout_wstring(stream, (wchar_t*)string, len);
673         else
674             written = streamout_astring(stream, (char*)string, len);
675         if (written == -1) return -1;
676         written_all += written;
677 
678 #if 0 && SUPPORT_FLOAT
679         /* Optional right '0' padding */
680         while (precision-- > 0)
681         {
682             if ((written = streamout_char(stream, _T('0'))) == 0) return -1;
683             written_all += written;
684             len++;
685         }
686 #endif
687 
688         /* Optional right padding */
689         if (flags & FLAG_ALIGN_LEFT)
690         {
691             while (padding-- > 0)
692             {
693                 if ((written = streamout_char(stream, _T(' '))) == 0) return -1;
694                 written_all += written;
695             }
696         }
697 
698     }
699 
700     if (written == -1) return -1;
701 
702     return written_all;
703 }
704 
705