+/* snprintf - formatted output to strings, with bounds checking and allocation */
+
/*
build a test version with
gcc -g -DDRIVER -I../.. -I../../include -o test-snprintf snprintf.c fmtu*long.o
Unix snprintf implementation.
derived from inetutils/libinetutils/snprintf.c Version 1.1
- Copyright (C) 2001 Free Software Foundation, Inc.
+ Copyright (C) 2001,2006,2010,2012 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General License as published by
- the Free Software Foundation; either version 2 of the License, or
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
+
+ Bash 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 General License for more details.
-
- You should have received a copy of the GNU General License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
- Revision History:
+ Original (pre-bash) Revision History:
1.1:
* added changes from Miles Bader
/*
* Currently doesn't handle (and bash/readline doesn't use):
- * *M$ width, precision specifications
- * %N$ numbered argument conversions
- * inf, nan floating values (could use isinf(), isnan())
- * `,', `'' flags
- * `C', `S' conversions
- * support for `F' is imperfect, since underlying printf may not handle it
+ * * *M$ width, precision specifications
+ * * %N$ numbered argument conversions
+ * * support for `F' is imperfect with ldfallback(), since underlying
+ * printf may not handle it -- should ideally have another autoconf test
*/
#define FLOATING_POINT
# include <config.h>
#endif
+/* GCC 4.2 on Snow Leopard doesn't like the snprintf prototype */
+#if defined(DEBUG) && !defined (MACOSX)
+# undef HAVE_SNPRINTF
+# undef HAVE_ASPRINTF
+
+# define HAVE_SNPRINTF 0
+# define HAVE_ASPRINTF 0
+#endif
+
#if defined(DRIVER) && !defined(HAVE_CONFIG_H)
#define HAVE_LONG_LONG
#define HAVE_LONG_DOUBLE
#ifdef __linux__
#define HAVE_PRINTF_A_FORMAT
#endif
+#define HAVE_ISINF_IN_LIBC
+#define HAVE_ISNAN_IN_LIBC
#define PREFER_STDARG
#define HAVE_STRINGIZE
#define HAVE_LIMITS_H
#define HAVE_STDDEF_H
+#define HAVE_LOCALE_H
#define intmax_t long
#endif
-#if !defined (HAVE_SNPRINTF) || !defined (HAVE_ASPRINTF)
+#if !HAVE_SNPRINTF || !HAVE_ASPRINTF
#include <bashtypes.h>
#endif
#ifdef FLOATING_POINT
+# include <float.h> /* for manifest constants */
# include <stdio.h> /* for sprintf */
#endif
#include <typemax.h>
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
#include "stdc.h"
+#include <shmbutil.h>
#ifndef DRIVER
# include "shell.h"
extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int));
#endif
+#ifndef FREE
+# define FREE(x) if (x) free (x)
+#endif
+
/* Bound on length of the string representing an integer value of type T.
Subtract one for the sign bit if T is signed;
302 / 1000 is log10 (2) rounded up;
#define PF_ZEROPAD 0x00008 /* 0 */
#define PF_PLUS 0x00010 /* + */
#define PF_SPACE 0x00020 /* ' ' */
-#define PF_COMMA 0x00040 /* , */
+#define PF_THOUSANDS 0x00040 /* ' */
#define PF_DOT 0x00080 /* `.precision' */
#define PF_STAR_P 0x00100 /* `*' after precision */
static char intbuf[INT_STRLEN_BOUND(unsigned long) + 1];
+static int decpoint;
+static int thoussep;
+static char *grouping;
+
/*
* For the FLOATING POINT FORMAT :
* the challenge was finding a way to
#define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;}
+#define GETARG(type) (va_arg(args, type))
+
/* Macros that do proper sign extension and handle length modifiers. Used
for the integer conversion specifiers. */
#define GETSIGNED(p) \
(((p)->flags & PF_LONGINT) \
- ? va_arg(args, long) \
- : (((p)->flags & PF_SHORTINT) ? (long)(short)va_arg(args, int) \
- : (long)va_arg(args, int)))
+ ? GETARG (long) \
+ : (((p)->flags & PF_SHORTINT) ? (long)(short)GETARG (int) \
+ : (long)GETARG (int)))
#define GETUNSIGNED(p) \
(((p)->flags & PF_LONGINT) \
- ? va_arg(args, unsigned long) \
- : (((p)->flags & PF_SHORTINT) ? (unsigned long)(unsigned short)va_arg(args, int) \
- : (unsigned long)va_arg(args, unsigned int)))
+ ? GETARG (unsigned long) \
+ : (((p)->flags & PF_SHORTINT) ? (unsigned long)(unsigned short)GETARG (int) \
+ : (unsigned long)GETARG (unsigned int)))
#ifdef HAVE_LONG_DOUBLE
-#define GETLDOUBLE(p) va_arg(args, long double)
+#define GETLDOUBLE(p) GETARG (long double)
#endif
-#define GETDOUBLE(p) va_arg(args, double)
+#define GETDOUBLE(p) GETARG (double)
#define SET_SIZE_FLAGS(p, type) \
if (sizeof (type) > sizeof (int)) \
static void dfallback __P((struct DATA *, const char *, const char *, double));
#endif
+static char *groupnum __P((char *));
+
+#if defined (HAVE_LONG_DOUBLE)
+# define LONGDOUBLE long double
+#else
+# define LONGDOUBLE double
+#endif
+
+#ifndef isnan
+ static inline int isnan_f (float x) { return x != x; }
+ static inline int isnan_d (double x) { return x != x; }
+ static inline int isnan_ld (LONGDOUBLE x) { return x != x; }
+ # define isnan(x) \
+ (sizeof (x) == sizeof (LONGDOUBLE) ? isnan_ld (x) \
+ : sizeof (x) == sizeof (double) ? isnan_d (x) \
+ : isnan_f (x))
+#endif
+
+#ifndef isinf
+ static inline int isinf_f (float x) { return !isnan (x) && isnan (x - x); }
+ static inline int isinf_d (double x) { return !isnan (x) && isnan (x - x); }
+ static inline int isinf_ld (LONGDOUBLE x) { return !isnan (x) && isnan (x - x); }
+ # define isinf(x) \
+ (sizeof (x) == sizeof (LONGDOUBLE) ? isinf_ld (x) \
+ : sizeof (x) == sizeof (double) ? isinf_d (x) \
+ : isinf_f (x))
+#endif
+
#ifdef DRIVER
static void memory_error_and_abort ();
static void *xmalloc __P((size_t));
} \
while (0)
+/* Output a string. P->WIDTH has already been adjusted for padding. */
+#define PUT_STRING(string, len, p) \
+ do \
+ { \
+ PAD_RIGHT (p); \
+ while ((len)-- > 0) \
+ { \
+ PUT_CHAR (*(string), (p)); \
+ (string)++; \
+ } \
+ PAD_LEFT (p); \
+ } \
+ while (0)
+
#define PUT_PLUS(d, p, zero) \
- if ((d) > zero && (p)->justify == RIGHT) \
+ if (((p)->flags & PF_PLUS) && (d) > zero) \
PUT_CHAR('+', p)
#define PUT_SPACE(d, p, zero) \
for (; (p)->width > 0; (p)->width--) \
PUT_CHAR((p)->pad, p)
+/* pad with zeros from decimal precision */
+#define PAD_ZERO(p) \
+ if ((p)->precision > 0) \
+ for (; (p)->precision > 0; (p)->precision--) \
+ PUT_CHAR('0', p)
+
/* if width and prec. in the args */
#define STAR_ARGS(p) \
+ do { \
if ((p)->flags & PF_STAR_W) \
- (p)->width = va_arg(args, int); \
+ { \
+ (p)->width = GETARG (int); \
+ if ((p)->width < 0) \
+ { \
+ (p)->flags |= PF_LADJUST; \
+ (p)->justify = LEFT; \
+ (p)->width = -(p)->width; \
+ } \
+ } \
if ((p)->flags & PF_STAR_P) \
- (p)->precision = va_arg(args, int)
+ { \
+ (p)->precision = GETARG (int); \
+ if ((p)->precision < 0) \
+ { \
+ (p)->flags &= ~PF_STAR_P; \
+ (p)->precision = NOT_FOUND; \
+ } \
+ } \
+ } while (0)
+
+#if defined (HAVE_LOCALE_H) && defined (HAVE_LOCALECONV)
+# define GETLOCALEDATA(d, t, g) \
+ do \
+ { \
+ struct lconv *lv; \
+ if ((d) == 0) { \
+ (d) = '.'; (t) = -1; (g) = 0; /* defaults */ \
+ lv = localeconv(); \
+ if (lv) \
+ { \
+ if (lv->decimal_point && lv->decimal_point[0]) \
+ (d) = lv->decimal_point[0]; \
+ if (lv->thousands_sep && lv->thousands_sep[0]) \
+ (t) = lv->thousands_sep[0]; \
+ (g) = lv->grouping ? lv->grouping : ""; \
+ if (*(g) == '\0' || *(g) == CHAR_MAX || (t) == -1) (g) = 0; \
+ } \
+ } \
+ } \
+ while (0);
+#else
+# define GETLOCALEDATA(d, t, g) \
+ ( (d) = '.', (t) = ',', g = "\003" )
+#endif
#ifdef FLOATING_POINT
/*
10^x ~= r
* log_10(200) = 2;
* log_10(250) = 2;
+ *
+ * NOTE: do not call this with r == 0 -- an infinite loop results.
*/
static int
log_10(r)
/*
* return an ascii representation of the integral part of the number
* and set fract to be an ascii representation of the fraction part
- * the container for the fraction and the integral part or staticly
+ * the container for the fraction and the integral part or statically
* declare with fix size
*/
static char *
register int i, j;
double ip, fp; /* integer and fraction part */
double fraction;
- int digits = MAX_INT - 1;
+ int digits, sign;
static char integral_part[MAX_INT];
static char fraction_part[MAX_FRACT];
- double sign;
int ch;
/* taking care of the obvious case: 0.0 */
{
integral_part[0] = '0';
integral_part[1] = '\0';
- fraction_part[0] = '0';
- fraction_part[1] = '\0';
+ /* The fractional part has to take the precision into account */
+ for (ch = 0; ch < precision-1; ch++)
+ fraction_part[ch] = '0';
+ fraction_part[ch] = '0';
+ fraction_part[ch+1] = '\0';
+ if (fract)
+ *fract = fraction_part;
return integral_part;
}
+ /* -0 is tricky */
+ sign = (number == -0.) ? '-' : ((number < 0.) ? '-' : '+');
+ digits = MAX_INT - 1;
+
/* for negative numbers */
- if ((sign = number) < 0.)
+ if (sign == '-')
{
number = -number;
digits--; /* sign consume one digit */
integral_part[i] = '9';
/* put the sign ? */
- if (sign < 0.)
+ if (sign == '-')
integral_part[i++] = '-';
integral_part[i] = '\0';
unsigned long d;
int base;
{
- char *tmp;
+ char *tmp, *t;
long sd;
int flags;
+ /* An explicit precision turns off the zero-padding flag and sets the
+ pad character back to space. */
+ if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+ {
+ p->flags &= ~PF_ZEROPAD;
+ p->pad = ' ';
+ }
+
sd = d; /* signed for ' ' padding in base 10 */
- flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+ flags = 0;
+ flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
if (*p->pf == 'X')
flags |= FL_HEXUPPER;
tmp = fmtulong (d, base, intbuf, sizeof(intbuf), flags);
- p->width -= strlen(tmp);
+ t = 0;
+ if ((p->flags & PF_THOUSANDS))
+ {
+ GETLOCALEDATA(decpoint, thoussep, grouping);
+ if (grouping && (t = groupnum (tmp)))
+ tmp = t;
+ }
+
+ /* need to add one for any `+', but we only add one in base 10 */
+ p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
PAD_RIGHT(p);
+ if ((p->flags & PF_DOT) && p->precision > 0)
+ {
+ p->precision -= strlen(tmp);
+ PAD_ZERO(p);
+ }
+
switch (base)
{
case 10:
}
PAD_LEFT(p);
+ FREE (t);
}
#ifdef HAVE_LONG_LONG
unsigned long long d;
int base;
{
- char *tmp;
+ char *tmp, *t;
long long sd;
int flags;
+ /* An explicit precision turns off the zero-padding flag and sets the
+ pad character back to space. */
+ if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+ {
+ p->flags &= ~PF_ZEROPAD;
+ p->pad = ' ';
+ }
+
sd = d; /* signed for ' ' padding in base 10 */
- flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+ flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
if (*p->pf == 'X')
flags |= FL_HEXUPPER;
tmp = fmtullong (d, base, intbuf, sizeof(intbuf), flags);
- p->width -= strlen(tmp);
+ t = 0;
+ if ((p->flags & PF_THOUSANDS))
+ {
+ GETLOCALEDATA(decpoint, thoussep, grouping);
+ if (grouping && (t = groupnum (tmp)))
+ tmp = t;
+ }
+
+ /* need to add one for any `+', but we only add one in base 10 */
+ p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
PAD_RIGHT(p);
+ if ((p->flags & PF_DOT) && p->precision > 0)
+ {
+ p->precision -= strlen(tmp);
+ PAD_ZERO(p);
+ }
+
switch (base)
{
case 10:
}
PAD_LEFT(p);
+ FREE (t);
}
#endif
PUT_CHAR(*tmp, p);
tmp++;
}
+
PAD_LEFT(p);
}
struct DATA *p;
char *tmp;
{
- int i;
+ size_t len;
- i = strlen(tmp);
+ len = strlen(tmp);
if (p->precision != NOT_FOUND) /* the smallest number */
- i = (i < p->precision ? i : p->precision);
- p->width -= i;
- PAD_RIGHT(p);
- while (i-- > 0)
- { /* put the sting */
- PUT_CHAR(*tmp, p);
- tmp++;
+ len = (len < p->precision ? len : p->precision);
+ p->width -= len;
+
+ PUT_STRING (tmp, len, p);
+}
+
+#if HANDLE_MULTIBYTE
+/* %ls wide-character strings */
+static void
+wstrings(p, tmp)
+ struct DATA *p;
+ wchar_t *tmp;
+{
+ size_t len;
+ mbstate_t mbs;
+ char *os;
+ const wchar_t *ws;
+
+ memset (&mbs, '\0', sizeof (mbstate_t));
+ ws = (const wchar_t *)tmp;
+
+ os = (char *)NULL;
+ if (p->precision != NOT_FOUND)
+ {
+ os = (char *)xmalloc (p->precision + 1);
+ len = wcsrtombs (os, &ws, p->precision, &mbs);
}
- PAD_LEFT(p);
+ else
+ {
+ len = wcsrtombs (NULL, &ws, 0, &mbs);
+ if (len != (size_t)-1)
+ {
+ memset (&mbs, '\0', sizeof (mbstate_t));
+ os = (char *)xmalloc (len + 1);
+ (void)wcsrtombs (os, &ws, len + 1, &mbs);
+ }
+ }
+ if (len == (size_t)-1)
+ {
+ /* invalid multibyte sequence; bail now. */
+ FREE (os);
+ return;
+ }
+
+ p->width -= len;
+ PUT_STRING (os, len, p);
+ free (os);
}
+static void
+wchars (p, wc)
+ struct DATA *p;
+ wint_t wc;
+{
+ char *lbuf, *l;
+ mbstate_t mbs;
+ size_t len;
+
+ lbuf = (char *)malloc (MB_CUR_MAX+1);
+ if (lbuf == 0)
+ return;
+ memset (&mbs, '\0', sizeof (mbstate_t));
+ len = wcrtomb (lbuf, wc, &mbs);
+ if (len == (size_t)-1)
+ /* conversion failed; bail now. */
+ return;
+ p->width -= len;
+ l = lbuf;
+ PUT_STRING (l, len, p);
+ free (lbuf);
+}
+#endif /* HANDLE_MULTIBYTE */
+
#ifdef FLOATING_POINT
+
+/* Check for [+-]infinity and NaN. If MODE == 1, we check for Infinity, else
+ (mode == 2) we check for NaN. This does the necessary printing. Returns
+ 1 if Inf or Nan, 0 if not. */
+static int
+chkinfnan(p, d, mode)
+ struct DATA *p;
+ double d;
+ int mode; /* == 1 for inf, == 2 for nan */
+{
+ int i;
+ char *tmp;
+ char *big, *small;
+
+ i = (mode == 1) ? isinf(d) : isnan(d);
+ if (i == 0)
+ return 0;
+ big = (mode == 1) ? "INF" : "NAN";
+ small = (mode == 1) ? "inf" : "nan";
+
+ tmp = (*p->pf == 'F' || *p->pf == 'G' || *p->pf == 'E') ? big : small;
+
+ if (i < 0)
+ PUT_CHAR('-', p);
+
+ while (*tmp)
+ {
+ PUT_CHAR (*tmp, p);
+ tmp++;
+ }
+
+ return 1;
+}
+
/* %f %F %g %G floating point representation */
static void
floating(p, d)
struct DATA *p;
double d;
{
- char *tmp, *tmp2;
+ char *tmp, *tmp2, *t;
int i;
+ if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
+ return; /* already printed nan or inf */
+
+ GETLOCALEDATA(decpoint, thoussep, grouping);
DEF_PREC(p);
d = ROUND(d, p);
tmp = dtoa(d, p->precision, &tmp2);
+ t = 0;
+ if ((p->flags & PF_THOUSANDS) && grouping && (t = groupnum (tmp)))
+ tmp = t;
+
+ if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
+ {
+ /* smash the trailing zeros unless altform */
+ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
+ tmp2[i] = '\0';
+ if (tmp2[0] == '\0')
+ p->precision = 0;
+ }
+
/* calculate the padding. 1 for the dot */
p->width = p->width -
+ /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
((d > 0. && p->justify == RIGHT) ? 1:0) -
+#else
+ ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
((p->flags & PF_SPACE) ? 1:0) -
- strlen(tmp) - p->precision - 1;
- PAD_RIGHT(p);
- PUT_PLUS(d, p, 0.);
+ strlen(tmp) - p->precision -
+ ((p->precision != 0 || (p->flags & PF_ALTFORM)) ? 1 : 0); /* radix char */
+
+ if (p->pad == ' ')
+ {
+ PAD_RIGHT(p);
+ PUT_PLUS(d, p, 0.);
+ }
+ else
+ {
+ if (*tmp == '-')
+ PUT_CHAR(*tmp++, p);
+ PUT_PLUS(d, p, 0.);
+ PAD_RIGHT(p);
+ }
PUT_SPACE(d, p, 0.);
+
while (*tmp)
- { /* the integral */
- PUT_CHAR(*tmp, p);
+ {
+ PUT_CHAR(*tmp, p); /* the integral */
tmp++;
}
+ FREE (t);
+
if (p->precision != 0 || (p->flags & PF_ALTFORM))
- PUT_CHAR('.', p); /* put the '.' */
- if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
- /* smash the trailing zeros unless altform */
- for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
- tmp2[i] = '\0';
+ PUT_CHAR(decpoint, p); /* put the '.' */
+
for (; *tmp2; tmp2++)
PUT_CHAR(*tmp2, p); /* the fraction */
double d;
{
char *tmp, *tmp2;
- int j, i, nsig, ndig;
+ int j, i;
+
+ if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
+ return; /* already printed nan or inf */
+ GETLOCALEDATA(decpoint, thoussep, grouping);
DEF_PREC(p);
- j = log_10(d);
- d = d / pow_10(j); /* get the Mantissa */
- d = ROUND(d, p);
+ if (d == 0.)
+ j = 0;
+ else
+ {
+ j = log_10(d);
+ d = d / pow_10(j); /* get the Mantissa */
+ d = ROUND(d, p);
+ }
tmp = dtoa(d, p->precision, &tmp2);
+
/* 1 for unit, 1 for the '.', 1 for 'e|E',
- * 1 for '+|-', 2 for 'exp' */
+ * 1 for '+|-', 2 for 'exp' (but no `.' if precision == 0 */
/* calculate how much padding need */
p->width = p->width -
+ /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
((d > 0. && p->justify == RIGHT) ? 1:0) -
- ((p->flags & PF_SPACE) ? 1:0) - p->precision - 6;
- PAD_RIGHT(p);
- PUT_PLUS(d, p, 0.);
+#else
+ ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
+ (p->precision != 0 || (p->flags & PF_ALTFORM)) -
+ ((p->flags & PF_SPACE) ? 1:0) - p->precision - 5;
+
+ if (p->pad == ' ')
+ {
+ PAD_RIGHT(p);
+ PUT_PLUS(d, p, 0.);
+ }
+ else
+ {
+ if (*tmp == '-')
+ PUT_CHAR(*tmp++, p);
+ PUT_PLUS(d, p, 0.);
+ PAD_RIGHT(p);
+ }
PUT_SPACE(d, p, 0.);
- /*
- * When supplied %g or %G, an optional precision is the number of
- * significant digits to print.
- *
- * nsig = number of significant digits we've printed (leading zeros are
- * never significant)
- * ndig = if non-zero, max number of significant digits to print (only
- * applicable to %g/%G)
- */
- nsig = ndig = 0;
- if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_DOT))
- ndig = (p->precision == 0) ? 1 : p->precision;
while (*tmp)
{
PUT_CHAR(*tmp, p);
tmp++;
- if (ndig && (++nsig >= ndig))
- break;
}
- if ((p->precision != 0 || (p->flags & PF_ALTFORM)) && (ndig == 0 || nsig < ndig))
- PUT_CHAR('.', p); /* the '.' */
+ if (p->precision != 0 || (p->flags & PF_ALTFORM))
+ PUT_CHAR(decpoint, p); /* the '.' */
+
if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
/* smash the trailing zeros unless altform */
for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
- tmp2[i] = '\0';
+ tmp2[i] = '\0';
+
for (; *tmp2; tmp2++)
- {
- if (ndig && (nsig++ >= ndig))
- break;
- PUT_CHAR(*tmp2, p); /* the fraction */
- }
+ PUT_CHAR(*tmp2, p); /* the fraction */
/* the exponent put the 'e|E' */
if (*p->pf == 'g' || *p->pf == 'e')
- {
- PUT_CHAR('e', p);
- }
+ PUT_CHAR('e', p);
else
- PUT_CHAR('E', p);
+ PUT_CHAR('E', p);
/* the sign of the exp */
- if (j > 0)
- {
- PUT_CHAR('+', p);
- }
+ if (j >= 0)
+ PUT_CHAR('+', p);
else
{
PUT_CHAR('-', p);
/* pad out to at least two spaces. pad with `0' if the exponent is a
single digit. */
if (j <= 9)
- {
- PUT_CHAR('0', p);
- }
+ PUT_CHAR('0', p);
/* the exponent */
while (*tmp)
PUT_CHAR(*tmp, p);
tmp++;
}
+
PAD_LEFT(p);
}
#endif
+/* Return a new string with the digits in S grouped according to the locale's
+ grouping info and thousands separator. If no grouping should be performed,
+ this returns NULL; the caller needs to check for it. */
+static char *
+groupnum (s)
+ char *s;
+{
+ char *se, *ret, *re, *g;
+ int len, slen;
+
+ if (grouping == 0 || *grouping <= 0 || *grouping == CHAR_MAX)
+ return ((char *)NULL);
+
+ /* find min grouping to size returned string */
+ for (len = *grouping, g = grouping; *g; g++)
+ if (*g > 0 && *g < len)
+ len = *g;
+
+ slen = strlen (s);
+ len = slen / len + 1;
+ ret = (char *)xmalloc (slen + len + 1);
+ re = ret + slen + len;
+ *re = '\0';
+
+ g = grouping;
+ se = s + slen;
+ len = *g;
+
+ while (se > s)
+ {
+ *--re = *--se;
+
+ /* handle `-' inserted by numtoa() and the fmtu* family here. */
+ if (se > s && se[-1] == '-')
+ continue;
+
+ /* begin new group. */
+ if (--len == 0 && se > s)
+ {
+ *--re = thoussep;
+ len = *++g; /* was g++, but that uses first char twice (glibc bug, too) */
+ if (*g == '\0')
+ len = *--g; /* use previous grouping */
+ else if (*g == CHAR_MAX)
+ {
+ do
+ *--re = *--se;
+ while (se > s);
+ break;
+ }
+ }
+ }
+
+ if (re > ret)
+#ifdef HAVE_MEMMOVE
+ memmove (ret, re, strlen (re) + 1);
+#else
+ strcpy (ret, re);
+#endif
+
+ return ret;
+}
+
/* initialize the conversion specifiers */
static void
init_conv_flag (p)
#endif
int state, i, c, n;
char *s;
+#if HANDLE_MULTIBYTE
+ wchar_t *ws;
+ wint_t wc;
+#endif
const char *convstart;
+ int negprec;
- /* Sanity check, the string must be > 1. C99 actually says that LENGTH
- can be zero here, in the case of snprintf/vsnprintf (it's never 0 in
- the case of asprintf/vasprintf), and the return value is the number
+ /* Sanity check, the string length must be >= 0. C99 actually says that
+ LENGTH can be zero here, in the case of snprintf/vsnprintf (it's never
+ 0 in the case of asprintf/vasprintf), and the return value is the number
of characters that would have been written. */
- if (length < 1)
+ if (length < 0)
return -1;
if (format == 0)
return 0;
+ /* Reset these for each call because the locale might have changed. */
+ decpoint = thoussep = 0;
+ grouping = 0;
+
+ negprec = 0;
for (; c = *(data->pf); data->pf++)
{
if (c != '%')
case '#':
data->flags |= PF_ALTFORM;
continue;
- case '0':
- data->flags |= PF_ZEROPAD;
- data->pad = '0';
- continue;
case '*':
if (data->flags & PF_DOT)
data->flags |= PF_STAR_P;
data->flags |= PF_STAR_W;
continue;
case '-':
- data->flags |= PF_LADJUST;
- data->justify = LEFT;
+ if ((data->flags & PF_DOT) == 0)
+ {
+ data->flags |= PF_LADJUST;
+ data->justify = LEFT;
+ }
+ else
+ negprec = 1;
continue;
case ' ':
if ((data->flags & PF_PLUS) == 0)
data->flags |= PF_SPACE;
continue;
case '+':
- data->flags |= PF_PLUS;
- data->justify = RIGHT;
+ if ((data->flags & PF_DOT) == 0)
+ {
+ data->flags |= PF_PLUS;
+ if ((data->flags & PF_LADJUST) == 0)
+ data->justify = RIGHT;
+ }
continue;
- case ',':
- data->flags |= PF_COMMA; /* not implemented yet */
+ case '\'':
+ data->flags |= PF_THOUSANDS;
continue;
+ case '0':
+ /* If we're not specifying precision (in which case we've seen
+ a `.') and we're not performing left-adjustment (in which
+ case the `0' is ignored), a `0' is taken as the zero-padding
+ flag. */
+ if ((data->flags & (PF_DOT|PF_LADJUST)) == 0)
+ {
+ data->flags |= PF_ZEROPAD;
+ data->pad = '0';
+ continue;
+ }
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
if (n < 0)
n = 0;
if (data->flags & PF_DOT)
- data->precision = n;
+ data->precision = negprec ? NOT_FOUND : n;
else
data->width = n;
continue;
STAR_ARGS(data);
DEF_PREC(data);
d = GETDOUBLE(data);
- i = log_10(d);
+ i = (d != 0.) ? log_10(d) : -1;
/*
* for '%g|%G' ANSI: use f if exponent
* is in the range or [-4,p] exclusively
* else use %e|%E
*/
if (-4 < i && i < data->precision)
- floating(data, d);
+ {
+ /* reset precision */
+ data->precision -= i + 1;
+ floating(data, d);
+ }
else
- exponent(data, d);
+ {
+ /* reduce precision by 1 because of leading digit before
+ decimal point in e format, unless specified as 0. */
+ if (data->precision > 0)
+ data->precision--;
+ exponent(data, d);
+ }
state = 0;
break;
case 'e':
#ifdef HAVE_LONG_LONG
if (data->flags & PF_LONGLONG)
{
- ull = va_arg(args, unsigned long long);
+ ull = GETARG (unsigned long long);
lnumber(data, ull, 10);
}
else
#ifdef HAVE_LONG_LONG
if (data->flags & PF_LONGLONG)
{
- ull = va_arg(args, long long);
+ ull = GETARG (long long);
lnumber(data, ull, 10);
}
else
#ifdef HAVE_LONG_LONG
if (data->flags & PF_LONGLONG)
{
- ull = va_arg(args, unsigned long long);
+ ull = GETARG (unsigned long long);
lnumber(data, ull, 8);
}
else
#ifdef HAVE_LONG_LONG
if (data->flags & PF_LONGLONG)
{
- ull = va_arg(args, unsigned long long);
+ ull = GETARG (unsigned long long);
lnumber(data, ull, 16);
}
else
break;
case 'p':
STAR_ARGS(data);
- ul = (unsigned long)va_arg(args, void *);
+ ul = (unsigned long)GETARG (void *);
pointer(data, ul);
state = 0;
break;
+#if HANDLE_MULTIBYTE
+ case 'C':
+ data->flags |= PF_LONGINT;
+ /* FALLTHROUGH */
+#endif
case 'c': /* character */
- ul = va_arg(args, int);
- PUT_CHAR(ul, data);
+ STAR_ARGS(data);
+#if HANDLE_MULTIBYTE
+ if (data->flags & PF_LONGINT)
+ {
+ wc = GETARG (wint_t);
+ wchars (data, wc);
+ }
+ else
+#endif
+ {
+ ul = GETARG (int);
+ PUT_CHAR(ul, data);
+ }
state = 0;
break;
+#if HANDLE_MULTIBYTE
+ case 'S':
+ data->flags |= PF_LONGINT;
+ /* FALLTHROUGH */
+#endif
case 's': /* string */
STAR_ARGS(data);
- s = va_arg(args, char *);
- strings(data, s);
+#if HANDLE_MULTIBYTE
+ if (data->flags & PF_LONGINT)
+ {
+ ws = GETARG (wchar_t *);
+ wstrings (data, ws);
+ }
+ else
+#endif
+ {
+ s = GETARG (char *);
+ strings(data, s);
+ }
state = 0;
break;
case 'n':
#ifdef HAVE_LONG_LONG
if (data->flags & PF_LONGLONG)
- *(va_arg(args, long long *)) = data->counter;
+ *(GETARG (long long *)) = data->counter;
else
#endif
if (data->flags & PF_LONGINT)
- *(va_arg(args, long *)) = data->counter;
+ *(GETARG (long *)) = data->counter;
else if (data->flags & PF_SHORTINT)
- *(va_arg(args, short *)) = data->counter;
+ *(GETARG (short *)) = data->counter;
else
- *(va_arg(args, int *)) = data->counter;
+ *(GETARG (int *)) = data->counter;
state = 0;
break;
case '%': /* nothing just % */
char fmtbuf[FALLBACK_FMTSIZE], *obuf;
int fl;
- obuf = xmalloc(LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2);
+ fl = LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2;
+ obuf = (char *)xmalloc (fl);
fl = fe - fs + 1;
strncpy (fmtbuf, fs, fl);
fmtbuf[fl] = '\0';
- sprintf (obuf, fmtbuf, ld);
+
+ if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+ sprintf (obuf, fmtbuf, data->width, data->precision, ld);
+ else if (data->flags & PF_STAR_W)
+ sprintf (obuf, fmtbuf, data->width, ld);
+ else if (data->flags & PF_STAR_P)
+ sprintf (obuf, fmtbuf, data->precision, ld);
+ else
+ sprintf (obuf, fmtbuf, ld);
+
for (x = obuf; *x; x++)
PUT_CHAR (*x, data);
xfree (obuf);
fl = fe - fs + 1;
strncpy (fmtbuf, fs, fl);
fmtbuf[fl] = '\0';
- sprintf (obuf, fmtbuf, d);
+
+ if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+ sprintf (obuf, fmtbuf, data->width, data->precision, d);
+ else if (data->flags & PF_STAR_W)
+ sprintf (obuf, fmtbuf, data->width, d);
+ else if (data->flags & PF_STAR_P)
+ sprintf (obuf, fmtbuf, data->precision, d);
+ else
+ sprintf (obuf, fmtbuf, d);
+
for (x = obuf; *x; x++)
PUT_CHAR (*x, data);
}
#endif /* FLOATING_POINT */
-#ifndef HAVE_SNPRINTF
+#if !HAVE_SNPRINTF
int
#if defined (__STDC__)
{
struct DATA data;
+ if (string == 0 && length != 0)
+ return 0;
init_data (&data, string, length, format, PFM_SN);
return (vsnprintf_internal(&data, string, length, format, args));
}
int rval;
va_list args;
-#if defined(PREFER_STDARG)
- va_start(args, format);
-#else
- va_start(args);
-#endif
+ SH_VA_START(args, format);
+ if (string == 0 && length != 0)
+ return 0;
init_data (&data, string, length, format, PFM_SN);
rval = vsnprintf_internal (&data, string, length, format, args);
#endif /* HAVE_SNPRINTF */
-#ifndef HAVE_ASPRINTF
+#if !HAVE_ASPRINTF
int
#if defined (__STDC__)
int rval;
va_list args;
-#if defined(PREFER_STDARG)
- va_start(args, format);
-#else
- va_start(args);
-#endif
+ SH_VA_START(args, format);
rval = vasprintf (stringp, format, args);
return rval;
}
-#endif
+#endif /* !HAVE_ASPRINTF */
-#endif
+#endif /* !HAVE_SNPRINTF || !HAVE_ASPRINTF */
#ifdef DRIVER
free (x);
}
-#ifdef FLOATING_POINT
-# include <float.h>
-#endif
-
/* set of small tests for snprintf() */
main()
{
char *h;
int i, si, ai;
+#ifdef HAVE_LOCALE_H
+ setlocale(LC_ALL, "");
+#endif
+
+#if 1
+ si = snprintf((char *)NULL, 0, "abcde\n");
+ printf("snprintf returns %d with NULL first argument and size of 0\n", si);
+ si = snprintf(holder, 0, "abcde\n");
+ printf("snprintf returns %d with non-NULL first argument and size of 0\n", si);
+ si = snprintf((char *)NULL, 16, "abcde\n");
+ printf("snprintf returns %d with NULL first argument and non-zero size\n", si);
+
/*
printf("Suite of test for snprintf:\n");
printf("a_format\n");
printf ("<%d> <%s>\n", si, holder);
printf ("<%d> <%s>\n\n", ai, h);
+ /* huh? */
+ printf("/%%g/, 421.2345\n");
+ snprintf(holder, sizeof holder, "/%g/\n", 421.2345);
+ asprintf(&h, "/%g/\n", 421.2345);
+ printf("/%g/\n", 421.2345);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%g/, 4214.2345\n");
+ snprintf(holder, sizeof holder, "/%g/\n", 4214.2345);
+ asprintf(&h, "/%g/\n", 4214.2345);
+ printf("/%g/\n", 4214.2345);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%.5g/, 4214.2345\n");
+ snprintf(holder, sizeof holder, "/%.5g/\n", 4214.2345);
+ asprintf(&h, "/%.5g/\n", 4214.2345);
+ printf("/%.5g/\n", 4214.2345);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%.4g/, 4214.2345\n");
+ snprintf(holder, sizeof holder, "/%.4g/\n", 4214.2345);
+ asprintf(&h, "/%.4g/\n", 4214.2345);
+ printf("/%.4g/\n", 4214.2345);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'ld %%'ld/, 12345, 1234567\n");
+ snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 12345, 1234567);
+ asprintf(&h, "/%'ld %'ld/\n", 12345, 1234567);
+ printf("/%'ld %'ld/\n", 12345, 1234567);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'ld %%'ld/, 336, 3336\n");
+ snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 336, 3336);
+ asprintf(&h, "/%'ld %'ld/\n", 336, 3336);
+ printf("/%'ld %'ld/\n", 336, 3336);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'ld %%'ld/, -42786, -142786\n");
+ snprintf(holder, sizeof holder, "/%'ld %'ld/\n", -42786, -142786);
+ asprintf(&h, "/%'ld %'ld/\n", -42786, -142786);
+ printf("/%'ld %'ld/\n", -42786, -142786);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'f %%'f/, 421.2345, 421234.56789\n");
+ snprintf(holder, sizeof holder, "/%'f %'f/\n", 421.2345, 421234.56789);
+ asprintf(&h, "/%'f %'f/\n", 421.2345, 421234.56789);
+ printf("/%'f %'f/\n", 421.2345, 421234.56789);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'f %%'f/, -421.2345, -421234.56789\n");
+ snprintf(holder, sizeof holder, "/%'f %'f/\n", -421.2345, -421234.56789);
+ asprintf(&h, "/%'f %'f/\n", -421.2345, -421234.56789);
+ printf("/%'f %'f/\n", -421.2345, -421234.56789);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'g %%'g/, 421.2345, 421234.56789\n");
+ snprintf(holder, sizeof holder, "/%'g %'g/\n", 421.2345, 421234.56789);
+ asprintf(&h, "/%'g %'g/\n", 421.2345, 421234.56789);
+ printf("/%'g %'g/\n", 421.2345, 421234.56789);
+ printf("%s", holder);
+ printf("%s\n", h);
+
+ printf("/%%'g %%'g/, -421.2345, -421234.56789\n");
+ snprintf(holder, sizeof holder, "/%'g %'g/\n", -421.2345, -421234.56789);
+ asprintf(&h, "/%'g %'g/\n", -421.2345, -421234.56789);
+ printf("/%'g %'g/\n", -421.2345, -421234.56789);
+ printf("%s", holder);
+ printf("%s\n", h);
+#endif
+
+ printf("/%%'g/, 4213455.8392\n");
+ snprintf(holder, sizeof holder, "/%'g/\n", 4213455.8392);
+ asprintf(&h, "/%'g/\n", 4213455.8392);
+ printf("/%'g/\n", 4213455.8392);
+ printf("%s", holder);
+ printf("%s\n", h);
+
exit (0);
}
#endif