diff options
Diffstat (limited to 'libquadmath/printf/quadmath-printf.c')
-rw-r--r-- | libquadmath/printf/quadmath-printf.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/libquadmath/printf/quadmath-printf.c b/libquadmath/printf/quadmath-printf.c new file mode 100644 index 000000000..b70f432cc --- /dev/null +++ b/libquadmath/printf/quadmath-printf.c @@ -0,0 +1,422 @@ +/* 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 |