summaryrefslogtreecommitdiff
path: root/printf/repl-vsnprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'printf/repl-vsnprintf.c')
-rw-r--r--printf/repl-vsnprintf.c373
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 = &prec;
+ 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;
+}