| /* GCC Quad-Precision Math Library | 
 |    Copyright (C) 2011 Free Software Foundation, Inc. | 
 |    Written by Jakub Jelinek  <jakub@redhat.com> | 
 |  | 
 | This file is part of the libquadmath library. | 
 | Libquadmath is free software; you can redistribute it and/or | 
 | modify it under the terms of the GNU Library General Public | 
 | License as published by the Free Software Foundation; either | 
 | version 2 of the License, or (at your option) any later version. | 
 |  | 
 | Libquadmath is distributed in the hope that it will be useful, | 
 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 | Library General Public License for more details. | 
 |  | 
 | You should have received a copy of the GNU Library General Public | 
 | License along with libquadmath; see the file COPYING.LIB.  If | 
 | not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, | 
 | Boston, MA 02110-1301, USA.  */ | 
 |  | 
 | #include <config.h> | 
 | #include <stdarg.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include "quadmath-printf.h" | 
 |  | 
 | /* Read a simple integer from a string and update the string pointer. | 
 |    It is assumed that the first character is a digit.  */ | 
 | static unsigned int | 
 | read_int (const char **pstr) | 
 | { | 
 |   unsigned int retval = (unsigned char) **pstr - '0'; | 
 |  | 
 |   while (isdigit ((unsigned char) *++(*pstr))) | 
 |     { | 
 |       retval *= 10; | 
 |       retval += (unsigned char) **pstr - '0'; | 
 |     } | 
 |  | 
 |   return retval; | 
 | } | 
 |  | 
 | #define PADSIZE 16 | 
 | static char const blanks[PADSIZE] = | 
 | {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; | 
 | static char const zeroes[PADSIZE] = | 
 | {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; | 
 | static wchar_t const wblanks[PADSIZE] = | 
 | { | 
 |   L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), | 
 |   L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' ') | 
 | }; | 
 | static wchar_t const wzeroes[PADSIZE] = | 
 | { | 
 |   L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), | 
 |   L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0') | 
 | }; | 
 |  | 
 | attribute_hidden size_t | 
 | __quadmath_do_pad (struct __quadmath_printf_file *fp, int wide, int c, | 
 | 		   size_t n) | 
 | { | 
 |   ssize_t i; | 
 |   char padbuf[PADSIZE]; | 
 |   wchar_t wpadbuf[PADSIZE]; | 
 |   const char *padstr; | 
 |   size_t w, written = 0; | 
 |   if (wide) | 
 |     { | 
 |       if (c == ' ') | 
 | 	padstr = (const char *) wblanks; | 
 |       else if (c == '0') | 
 | 	padstr = (const char *) wzeroes; | 
 |       else | 
 | 	{ | 
 | 	  padstr = (const char *) wpadbuf; | 
 | 	  for (i = 0; i < PADSIZE; i++) | 
 | 	    wpadbuf[i] = c; | 
 | 	} | 
 |     } | 
 |   else | 
 |     { | 
 |       if (c == ' ') | 
 | 	padstr = blanks; | 
 |       else if (c == '0') | 
 | 	padstr = zeroes; | 
 |       else | 
 | 	{ | 
 | 	  padstr = (const char *) padbuf; | 
 | 	  for (i = 0; i < PADSIZE; i++) | 
 | 	    padbuf[i] = c; | 
 | 	} | 
 |     } | 
 |   for (i = n; i >= PADSIZE; i -= PADSIZE) | 
 |     { | 
 |       w = PUT (fp, (char *) padstr, PADSIZE); | 
 |       written += w; | 
 |       if (w != PADSIZE) | 
 | 	return written; | 
 |     } | 
 |   if (i > 0) | 
 |     { | 
 |       w = PUT (fp, (char *) padstr, i); | 
 |       written += w; | 
 |     } | 
 |   return written; | 
 | } | 
 |  | 
 | /* This is a stripped down version of snprintf, which just handles | 
 |    a single %eEfFgGaA format entry with Q modifier.  % has to be | 
 |    the first character of the format string, no $ can be used.  */ | 
 | int | 
 | quadmath_snprintf (char *str, size_t size, const char *format, ...) | 
 | { | 
 |   struct printf_info info; | 
 |   va_list ap; | 
 |   __float128 fpnum, *fpnum_addr = &fpnum, **fpnum_addr2 = &fpnum_addr; | 
 |   struct __quadmath_printf_file qfp; | 
 |  | 
 |   if (*format++ != '%') | 
 |     return -1; | 
 |  | 
 |   /* Clear information structure.  */ | 
 |   memset (&info, '\0', sizeof info); | 
 |   /* info.alt = 0; | 
 |   info.space = 0; | 
 |   info.left = 0; | 
 |   info.showsign = 0; | 
 |   info.group = 0; | 
 |   info.i18n = 0; | 
 |   info.extra = 0; */ | 
 |   info.pad = ' '; | 
 |   /* info.wide = 0; */ | 
 |  | 
 |   /* Check for spec modifiers.  */ | 
 |   do | 
 |     { | 
 |       switch (*format) | 
 | 	{ | 
 | 	case ' ': | 
 | 	  /* Output a space in place of a sign, when there is no sign.  */ | 
 | 	  info.space = 1; | 
 | 	  continue; | 
 | 	case '+': | 
 | 	  /* Always output + or - for numbers.  */ | 
 | 	  info.showsign = 1; | 
 | 	  continue; | 
 | 	case '-': | 
 | 	  /* Left-justify things.  */ | 
 | 	  info.left = 1; | 
 | 	  continue; | 
 | 	case '#': | 
 | 	  /* Use the "alternate form": | 
 | 	     Hex has 0x or 0X, FP always has a decimal point.  */ | 
 | 	  info.alt = 1; | 
 | 	  continue; | 
 | 	case '0': | 
 | 	  /* Pad with 0s.  */ | 
 | 	  info.pad = '0'; | 
 | 	  continue; | 
 | 	case '\'': | 
 | 	  /* Show grouping in numbers if the locale information | 
 | 	     indicates any.  */ | 
 | 	  info.group = 1; | 
 | 	  continue; | 
 | 	case 'I': | 
 | 	  /* Use the internationalized form of the output.  Currently | 
 | 	     means to use the `outdigits' of the current locale.  */ | 
 | 	  info.i18n = 1; | 
 | 	  continue; | 
 | 	default: | 
 | 	  break; | 
 | 	} | 
 |       break; | 
 |     } | 
 |   while (*++format); | 
 |  | 
 |   if (info.left) | 
 |     info.pad = ' '; | 
 |  | 
 |   va_start (ap, format); | 
 |  | 
 |   /* Get the field width.  */ | 
 |   /* info.width = 0; */ | 
 |   if (*format == '*') | 
 |     { | 
 |       /* The field width is given in an argument. | 
 | 	 A negative field width indicates left justification.  */ | 
 |       ++format; | 
 |       info.width = va_arg (ap, int); | 
 |     } | 
 |   else if (isdigit (*format)) | 
 |     /* Constant width specification.  */ | 
 |     info.width = read_int (&format); | 
 |  | 
 |   /* Get the precision.  */ | 
 |   /* -1 means none given; 0 means explicit 0.  */ | 
 |   info.prec = -1; | 
 |   if (*format == '.') | 
 |     { | 
 |       ++format; | 
 |       if (*format == '*') | 
 | 	{ | 
 | 	  /* The precision is given in an argument.  */ | 
 | 	  ++format; | 
 |  | 
 | 	  info.prec = va_arg (ap, int); | 
 | 	} | 
 |       else if (isdigit (*format)) | 
 | 	info.prec = read_int (&format); | 
 |       else | 
 | 	/* "%.?" is treated like "%.0?".  */ | 
 | 	info.prec = 0; | 
 |     } | 
 |  | 
 |   /* Check for type modifiers.  */ | 
 |   /* info.is_long_double = 0; | 
 |   info.is_short = 0; | 
 |   info.is_long = 0; | 
 |   info.is_char = 0; | 
 |   info.user = 0; */ | 
 |  | 
 |   /* We require Q modifier.  */ | 
 |   if (*format++ != 'Q') | 
 |     { | 
 |       va_end (ap); | 
 |       return -1; | 
 |     } | 
 |  | 
 |   /* Get the format specification.  */ | 
 |   info.spec = (wchar_t) *format++; | 
 |   if (info.spec == L_('\0') || *format != '\0') | 
 |     { | 
 |       va_end (ap); | 
 |       return -1; | 
 |     } | 
 |  | 
 |   switch (info.spec) | 
 |     { | 
 |     case L_('e'): | 
 |     case L_('E'): | 
 |     case L_('f'): | 
 |     case L_('F'): | 
 |     case L_('g'): | 
 |     case L_('G'): | 
 |     case L_('a'): | 
 |     case L_('A'): | 
 |       break; | 
 |     default: | 
 |       va_end (ap); | 
 |       return -1; | 
 |     } | 
 |  | 
 |   fpnum = va_arg (ap, __float128); | 
 |   va_end (ap); | 
 |  | 
 |   qfp.fp = NULL; | 
 |   qfp.str = str; | 
 |   qfp.size = size ? size - 1 : 0; | 
 |   qfp.len = 0; | 
 |   qfp.file_p = 0; | 
 |  | 
 |   if (info.spec == L_('a') || info.spec == L_('A')) | 
 |     __quadmath_printf_fphex (&qfp, &info, (const void *const *)&fpnum_addr2); | 
 |   else | 
 |     __quadmath_printf_fp (&qfp, &info, (const void *const *)&fpnum_addr2); | 
 |  | 
 |   if (size) | 
 |     *qfp.str = '\0'; | 
 |  | 
 |   return qfp.len; | 
 | } | 
 |  | 
 | #ifdef HAVE_PRINTF_HOOKS | 
 | static int pa_flt128; | 
 | int mod_Q attribute_hidden; | 
 |  | 
 | static void | 
 | flt128_va (void *mem, va_list *ap) | 
 | {  | 
 |   __float128 d = va_arg (*ap, __float128); | 
 |   memcpy (mem, &d, sizeof (d)); | 
 | } | 
 |  | 
 | static int | 
 | flt128_ais (const struct printf_info *info, size_t n __attribute__ ((unused)), | 
 | 	    int *argtype, int *size) | 
 | { | 
 |   if (info->user & mod_Q) | 
 |     { | 
 |       argtype[0] = pa_flt128; | 
 |       size[0] = sizeof (__float128); | 
 |       return 1; | 
 |     } | 
 | #if __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 13) | 
 |   /* Workaround bug in glibc printf hook handling.  */ | 
 |   size[0] = -1; | 
 |   switch (info->spec) | 
 |     { | 
 |     case L_('i'): | 
 |     case L_('d'): | 
 |     case L_('u'): | 
 |     case L_('o'): | 
 |     case L_('X'): | 
 |     case L_('x'): | 
 | #if __LONG_MAX__ != __LONG_LONG_MAX__ | 
 |       if (info->is_long_double) | 
 | 	argtype[0] = PA_INT|PA_FLAG_LONG_LONG; | 
 |       else | 
 | #endif | 
 |       if (info->is_long) | 
 | 	argtype[0] = PA_INT|PA_FLAG_LONG; | 
 |       else if (info->is_short) | 
 | 	argtype[0] = PA_INT|PA_FLAG_SHORT; | 
 |       else if (info->is_char) | 
 | 	argtype[0] = PA_CHAR; | 
 |       else | 
 | 	argtype[0] = PA_INT; | 
 |       return 1; | 
 |     case L_('e'): | 
 |     case L_('E'): | 
 |     case L_('f'): | 
 |     case L_('F'): | 
 |     case L_('g'): | 
 |     case L_('G'): | 
 |     case L_('a'): | 
 |     case L_('A'): | 
 |       if (info->is_long_double) | 
 | 	argtype[0] = PA_DOUBLE|PA_FLAG_LONG_DOUBLE; | 
 |       else | 
 | 	argtype[0] = PA_DOUBLE; | 
 |       return 1; | 
 |     case L_('c'): | 
 |       argtype[0] = PA_CHAR; | 
 |       return 1; | 
 |     case L_('C'): | 
 |       argtype[0] = PA_WCHAR; | 
 |       return 1; | 
 |     case L_('s'): | 
 |       argtype[0] = PA_STRING; | 
 |       return 1; | 
 |     case L_('S'): | 
 |       argtype[0] = PA_WSTRING; | 
 |       return 1; | 
 |     case L_('p'): | 
 |       argtype[0] = PA_POINTER; | 
 |       return 1; | 
 |     case L_('n'): | 
 |       argtype[0] = PA_INT|PA_FLAG_PTR; | 
 |       return 1; | 
 |  | 
 |     case L_('m'): | 
 |     default: | 
 |       /* An unknown spec will consume no args.  */ | 
 |       return 0; | 
 |     } | 
 | #endif | 
 |   return -1; | 
 | } | 
 |  | 
 | static int | 
 | flt128_printf_fp (FILE *fp, const struct printf_info *info, | 
 | 		  const void *const *args) | 
 | { | 
 |   struct __quadmath_printf_file qpf | 
 |     = { .fp = fp, .str = NULL, .size = 0, .len = 0, .file_p = 1 }; | 
 |  | 
 |   if ((info->user & mod_Q) == 0) | 
 |     return -2; | 
 |  | 
 |   return __quadmath_printf_fp (&qpf, info, args); | 
 | } | 
 |  | 
 | static int | 
 | flt128_printf_fphex (FILE *fp, const struct printf_info *info, | 
 | 		     const void *const *args) | 
 | { | 
 |   struct __quadmath_printf_file qpf | 
 |     = { .fp = fp, .str = NULL, .size = 0, .len = 0, .file_p = 1 }; | 
 |  | 
 |   if ((info->user & mod_Q) == 0) | 
 |     return -2; | 
 |  | 
 |   return __quadmath_printf_fphex (&qpf, info, args); | 
 | } | 
 |  | 
 | __attribute__((constructor)) static void | 
 | register_printf_flt128 (void) | 
 | { | 
 |   pa_flt128 = register_printf_type (flt128_va); | 
 |   if (pa_flt128 == -1) | 
 |     return; | 
 |   mod_Q = register_printf_modifier (L_("Q")); | 
 |   if (mod_Q == -1) | 
 |     return; | 
 |   register_printf_specifier ('f', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('F', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('e', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('E', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('g', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('G', flt128_printf_fp, flt128_ais); | 
 |   register_printf_specifier ('a', flt128_printf_fphex, flt128_ais); | 
 |   register_printf_specifier ('A', flt128_printf_fphex, flt128_ais); | 
 | } | 
 |  | 
 | __attribute__((destructor)) static void | 
 | unregister_printf_flt128 (void) | 
 | { | 
 |   /* No way to unregister printf type and modifier currently, | 
 |      and only one printf specifier can be registered right now.  */ | 
 |   if (pa_flt128 == -1 || mod_Q == -1) | 
 |     return; | 
 |   register_printf_specifier ('f', NULL, NULL); | 
 |   register_printf_specifier ('F', NULL, NULL); | 
 |   register_printf_specifier ('e', NULL, NULL); | 
 |   register_printf_specifier ('E', NULL, NULL); | 
 |   register_printf_specifier ('g', NULL, NULL); | 
 |   register_printf_specifier ('G', NULL, NULL); | 
 |   register_printf_specifier ('a', NULL, NULL); | 
 |   register_printf_specifier ('A', NULL, NULL); | 
 | } | 
 | #endif |