diff options
Diffstat (limited to 'printf/repl-vsnprintf.c')
-rw-r--r-- | printf/repl-vsnprintf.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/printf/repl-vsnprintf.c b/printf/repl-vsnprintf.c new file mode 100644 index 000000000..a6bbfbf60 --- /dev/null +++ b/printf/repl-vsnprintf.c @@ -0,0 +1,373 @@ +/* __gmp_replacement_vsnprintf -- for systems which don't have vsnprintf, or + only have a broken one. + + THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY. THEY'RE ALMOST + CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN + FUTURE GNU MP RELEASES. + +Copyright 2001 Free Software Foundation, Inc. + +This file is part of the GNU MP Library. + +The GNU MP Library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your +option) any later version. + +The GNU MP Library 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 Lesser General Public +License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the GNU MP Library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, +MA 02111-1307, USA. */ + +#define _GNU_SOURCE /* for strnlen prototype */ + +#include "config.h" + +#if HAVE_STDARG +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +#include <ctype.h> /* for isdigit */ +#include <float.h> /* for DBL_MAX_10_EXP etc */ +#include <stddef.h> /* for ptrdiff_t */ +#include <string.h> +#include <stdio.h> /* for NULL */ +#include <stdlib.h> + +#if HAVE_STDINT_H +#include <stdint.h> /* for intmax_t */ +#endif + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> /* for quad_t */ +#endif + +#include "gmp.h" +#include "gmp-impl.h" + + +#if ! HAVE_STRNLEN +static size_t +strnlen (const char *s, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) + if (s[i] == '\0') + break; + return i; +} +#endif + + +/* The approach here is to parse the fmt string, and decide how much space + it requires, then use vsprintf into a big enough buffer. The space + calculated isn't an exact amount, but it's certainly no less than + required. + + This code was inspired by GNU libiberty/vasprintf.c but we support more + datatypes, when available. + + mingw32 - doesn't have vsnprintf, it seems. Because gcc is used a full + set of types are available, but "long double" is just a plain IEEE + 64-bit "double", so we avoid the big 15-bit exponent estimate + (LDBL_MAX_EXP_10 is defined). */ + +int +__gmp_replacement_vsnprintf (char *buf, size_t buf_size, + const char *orig_fmt, va_list orig_ap) +{ + va_list ap; + const char *fmt; + size_t total_width, integer_sizeof, floating_sizeof, len; + char fchar, type; + int width, prec, seen_prec, double_digits, long_double_digits; + int *value; + + /* preserve orig_ap for use after size estimation */ + va_copy (ap, orig_ap); + + fmt = orig_fmt; + total_width = strlen (fmt) + 1; /* 1 extra for the '\0' */ + + integer_sizeof = sizeof (long); +#if HAVE_LONG_LONG + integer_sizeof = MAX (integer_sizeof, sizeof (long long)); +#endif +#if HAVE_QUAD_T + integer_sizeof = MAX (integer_sizeof, sizeof (quad_t)); +#endif + + floating_sizeof = sizeof (double); +#if HAVE_LONG_DOUBLE + floating_sizeof = MAX (floating_sizeof, sizeof (long double)); +#endif + + /* IEEE double or VAX G floats have an 11 bit exponent, so the default is + a maximum 308 decimal digits. VAX D floats have only an 8 bit + exponent, but we don't bother trying to detect that directly. */ + double_digits = 308; +#ifdef DBL_MAX_10_EXP + /* but case prefer a value the compiler says */ + double_digits = DBL_MAX_10_EXP; +#endif + + /* IEEE 128-bit quad, Intel 80-bit temporary, or VAX H floats all have 15 + bit exponents, so the default is a maximum 4932 decimal digits. */ + long_double_digits = 4932; + /* but if double == long double, then go with that size */ + if (sizeof (double) == sizeof (long double)) + long_double_digits = double_digits; +#ifdef LDBL_MAX_10_EXP + /* but in any case prefer a value the compiler says */ + long_double_digits = LDBL_MAX_10_EXP; +#endif + + for (;;) + { + fmt = strchr (fmt, '%'); + if (fmt == NULL) + break; + + type = '\0'; + width = 0; + prec = 6; + seen_prec = 0; + value = &width; + + for (;;) + { + fchar = *fmt++; + switch (fchar) { + + case 'c': + /* char, already accounted for by strlen(fmt) */ + goto next; + + case 'd': + case 'i': + case 'o': + case 'x': + case 'X': + case 'u': + /* at most 3 digits per byte in hex, dec or octal, plus a sign */ + total_width += 3 * integer_sizeof + 1; + + switch (type) { + case 'j': + /* Let's assume uintmax_t is the same size as intmax_t. */ +#if HAVE_INTMAX_T + (void) va_arg (ap, intmax_t); +#else + ASSERT_FAIL (intmax_t not available); +#endif + break; + case 'l': + (void) va_arg (ap, long); + break; + case 'L': +#if HAVE_LONG_LONG + (void) va_arg (ap, long long); +#else + ASSERT_FAIL (long long not available); +#endif + break; + case 'q': + /* quad_t is probably the same as long long, but let's treat + it separately just to be sure. Also let's assume u_quad_t + will be the same size as quad_t. */ +#if HAVE_QUAD_T + (void) va_arg (ap, quad_t); +#else + ASSERT_FAIL (quad_t not available); +#endif + break; + case 't': +#if HAVE_PTRDIFF_T + (void) va_arg (ap, ptrdiff_t); +#else + ASSERT_FAIL (ptrdiff_t not available); +#endif + break; + case 'z': + (void) va_arg (ap, size_t); + break; + default: + /* default is an "int", and this includes h=short and hh=char + since they're promoted to int in a function call */ + (void) va_arg (ap, int); + break; + } + goto next; + + case 'E': + case 'e': + case 'G': + case 'g': + /* Requested decimals, sign, point and e, plus an overestimate + of exponent digits (the assumption is all the float is + exponent!). */ + total_width += prec + 3 + floating_sizeof * 3; + if (type == 'L') + { +#if HAVE_LONG_DOUBLE + (void) va_arg (ap, long double); +#else + ASSERT_FAIL (long double not available); +#endif + } + else + (void) va_arg (ap, double); + break; + + case 'f': + /* Requested decimals, sign and point, and a margin for error, + then add the maximum digits that can be in the integer part, + based on the maximum exponent value. */ + total_width += prec + 2 + 10; + if (type == 'L') + { +#if HAVE_LONG_DOUBLE + (void) va_arg (ap, long double); + total_width += long_double_digits; +#else + ASSERT_FAIL (long double not available); +#endif + } + else + { + (void) va_arg (ap, double); + total_width += double_digits; + } + break; + + case 'h': /* short or char */ + case 'j': /* intmax_t */ + case 'L': /* long long or long double */ + case 'q': /* quad_t */ + case 't': /* ptrdiff_t */ + set_type: + type = fchar; + break; + + case 'l': + /* long or long long */ + if (type != 'l') + goto set_type; + type = 'L'; /* "ll" means "L" */ + break; + + case 'n': + /* bytes written, no output as such */ + (void) va_arg (ap, void *); + goto next; + + case 's': + /* If no precision was given, then determine the string length + and put it there, to be added to the total under "next". If + a precision was given then that's already the maximum from + this field, but see whether the string is shorter than that, + in case the limit was very big. */ + { + const char *s = va_arg (ap, const char *); + prec = (seen_prec ? strnlen (s, prec) : strlen (s)); + } + goto next; + + case 'p': + /* pointer, let's assume at worst it's octal with some padding */ + (void) va_arg (ap, const void *); + total_width += 3 * sizeof (void *) + 16; + goto next; + + case '%': + /* literal %, already accounted for by strlen(fmt) */ + goto next; + + case '#': + /* showbase, at most 2 for "0x" */ + total_width += 2; + break; + + case '+': + case ' ': + /* sign, already accounted for under numerics */ + break; + + case '-': + /* left justify, no effect on total width */ + break; + + case '.': + seen_prec = 1; + value = ≺ + break; + + case '*': + { + /* negative width means left justify which can be ignored, + negative prec would be invalid, just use absolute value */ + int n = va_arg (ap, int); + *value = ABS (n); + } + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* process all digits to form a value */ + { + int n = 0; + do { + n = n * 10 + (fchar-'0'); + fchar = *fmt++; + } while (isascii (fchar) && isdigit (fchar)); + fmt--; /* unget the non-digit */ + *value = n; + } + break; + + default: + /* incomplete or invalid % sequence */ + ASSERT (0); + goto next; + } + } + + next: + total_width += width; + total_width += prec; + } + + if (total_width <= buf_size) + { + vsprintf (buf, orig_fmt, orig_ap); + len = strlen (buf); + } + else + { + char *s; + + s = (*__gmp_allocate_func) (total_width); + vsprintf (s, orig_fmt, orig_ap); + len = strlen (s); + if (buf_size != 0) + { + size_t copylen = MIN (len, buf_size-1); + memcpy (buf, s, copylen); + buf[copylen] = '\0'; + } + (*__gmp_free_func) (s, total_width); + } + + /* If total_width was somehow wrong then chances are we've already + clobbered memory, but maybe this check will still work. */ + ASSERT_ALWAYS (len < buf_size); + + return len; +} |