/* vsprintf with automatic memory allocation.
   Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc.

   This program 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, or (at your option)
   any later version.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
   USA.  */

#ifndef _WIN32
/* Tell glibc's <stdio.h> to provide a prototype for snprintf().
   This must come before <config.h> because <config.h> may include
   <features.h>, and once <features.h> has been included, it's too late.  */
#ifndef _GNU_SOURCE
# define _GNU_SOURCE    1
#endif
#endif

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "gst-printf.h"

/* Specification.  */
#include "vasnprintf.h"

#include <stdio.h>              /* snprintf(), sprintf() */
#include <stdlib.h>             /* abort(), malloc(), realloc(), free() */
#include <string.h>             /* memcpy(), strlen() */
#include <errno.h>              /* errno */
#include <limits.h>             /* CHAR_BIT */
#include <float.h>              /* DBL_MAX_EXP, LDBL_MAX_EXP */
#include "printf-parse.h"
#include "printf-extension.h"

#ifdef HAVE_WCHAR_T
# ifdef HAVE_WCSLEN
#  define local_wcslen wcslen
# else
   /* Solaris 2.5.1 has wcslen() in a separate library libw.so. To avoid
      a dependency towards this library, here is a local substitute.
      Define this substitute only once, even if this file is included
      twice in the same compilation unit.  */
#  ifndef local_wcslen_defined
#   define local_wcslen_defined 1
static size_t
local_wcslen (const wchar_t * s)
{
  const wchar_t *ptr;

  for (ptr = s; *ptr != (wchar_t) 0; ptr++);
  return ptr - s;
}
#  endif
# endif
#endif

/* For those losing systems which don't have 'alloca' we have to add
   some additional code emulating it.  */
#if defined (alloca) || defined (GLIB_HAVE_ALLOCA_H)
# define freea(p)               /* nothing */
#else
# define alloca(n) malloc (n)
# define freea(p) free (p)
#endif

#ifndef HAVE_LONG_LONG_FORMAT
static inline int
print_long_long (char *buf,
    int len,
    int width,
    int precision,
    unsigned long flags, char conversion, unsigned long long number)
{
  int negative = FALSE;
  char buffer[128];
  char *bufferend;
  char *pointer;
  int base;
  static const char *upper = "0123456789ABCDEFX";
  static const char *lower = "0123456789abcdefx";
  const char *digits;
  int i;
  char *p;
  int count;

#define EMIT(c)           \
  if (p - buf == len - 1) \
    {                     \
      *p++ = '\0';        \
      return len;         \
    }                     \
  else                    \
    *p++ = c;

  p = buf;

  switch (conversion) {
    case 'o':
      base = 8;
      digits = lower;
      negative = FALSE;
      break;
    case 'x':
      base = 16;
      digits = lower;
      negative = FALSE;
      break;
    case 'X':
      base = 16;
      digits = upper;
      negative = FALSE;
      break;
    case 'u':
      base = 10;
      digits = lower;
      negative = FALSE;
      break;
    default:
      base = 10;
      digits = lower;
      negative = (long long) number < 0;
      if (negative)
        number = -((long long) number);
      break;
  }

  /* Build number */
  pointer = bufferend = &buffer[sizeof (buffer) - 1];
  *pointer-- = '\0';
  for (i = 1; i < (int) sizeof (buffer); i++) {
    *pointer-- = digits[number % base];
    number /= base;
    if (number == 0)
      break;
  }

  /* Adjust width */
  width -= (bufferend - pointer) - 1;

  /* Adjust precision */
  if (precision != -1) {
    precision -= (bufferend - pointer) - 1;
    if (precision < 0)
      precision = 0;
    flags |= FLAG_ZERO;
  }

  /* Adjust width further */
  if (negative || (flags & FLAG_SHOWSIGN) || (flags & FLAG_SPACE))
    width--;
  if (flags & FLAG_ALT) {
    switch (base) {
      case 16:
        width -= 2;
        break;
      case 8:
        width--;
        break;
      default:
        break;
    }
  }

  /* Output prefixes spaces if needed */
  if (!((flags & FLAG_LEFT) || ((flags & FLAG_ZERO) && (precision == -1)))) {
    count = (precision == -1) ? 0 : precision;
    while (width-- > count)
      *p++ = ' ';
  }

  /* width has been adjusted for signs and alternatives */
  if (negative) {
    EMIT ('-');
  } else if (flags & FLAG_SHOWSIGN) {
    EMIT ('+');
  } else if (flags & FLAG_SPACE) {
    EMIT (' ');
  }

  if (flags & FLAG_ALT) {
    switch (base) {
      case 8:
        EMIT ('0');
        break;
      case 16:
        EMIT ('0');
        EMIT (digits[16]);
        break;
      default:
        break;
    }                           /* switch base */
  }

  /* Output prefixed zero padding if needed */
  if (flags & FLAG_ZERO) {
    if (precision == -1)
      precision = width;
    while (precision-- > 0) {
      EMIT ('0');
      width--;
    }
  }

  /* Output the number itself */
  while (*(++pointer)) {
    EMIT (*pointer);
  }

  /* Output trailing spaces if needed */
  if (flags & FLAG_LEFT) {
    while (width-- > 0)
      EMIT (' ');
  }

  EMIT ('\0');

  return p - buf - 1;
}
#endif

static void
printf_postprocess_args (char_directives * directives, arguments * arguments)
{
  int i;

  for (i = 0; i < directives->count; ++i) {
    char_directive *dp;
    argument *a;

    dp = &directives->dir[i];

    /* %% has no arguments, for example */
    if (dp->arg_index < 0)
      continue;

    a = &arguments->arg[dp->arg_index];

    if (a->type == TYPE_POINTER_EXT) {
      char fmt[4];

      fmt[0] = 'p';
      fmt[1] = POINTER_EXT_SIGNIFIER_CHAR;
      fmt[2] = dp->ptr_ext_char;
      fmt[3] = '\0';

      a->ext_string =
          __gst_printf_pointer_extension_serialize (fmt, a->a.a_pointer);
    }
  }
}

char *
vasnprintf (char *resultbuf, size_t * lengthp, const char *format, va_list args)
{
  char_directives d;
  arguments a;

  if (printf_parse (format, &d, &a) < 0) {
    errno = EINVAL;
    return NULL;
  }
#define CLEANUP()                         \
  free (d.dir);                           \
  if (a.arg) {                            \
    while (a.count--) {                   \
      if (a.arg[a.count].ext_string)      \
        free (a.arg[a.count].ext_string); \
    }                                     \
    free (a.arg);                         \
  }

  if (printf_fetchargs (args, &a) < 0) {
    CLEANUP ();
    errno = EINVAL;
    return NULL;
  }

  /* collect TYPE_POINTER_EXT argument strings */
  printf_postprocess_args (&d, &a);

  {
    char *buf =
        (char *) alloca (7 + d.max_width_length + d.max_precision_length + 6);
    const char *cp;
    unsigned int i;
    char_directive *dp;
    /* Output string accumulator.  */
    char *result;
    size_t allocated;
    size_t length;

    if (resultbuf != NULL) {
      result = resultbuf;
      allocated = *lengthp;
    } else {
      result = NULL;
      allocated = 0;
    }
    length = 0;
    /* Invariants:
       result is either == resultbuf or == NULL or malloc-allocated.
       If length > 0, then result != NULL.  */

#define ENSURE_ALLOCATION(needed) \
    if ((needed) > allocated)						\
      {									\
	char *memory;							\
									\
	allocated = (allocated > 0 ? 2 * allocated : 12);		\
	if ((needed) > allocated)					\
	  allocated = (needed);						\
	if (result == resultbuf || result == NULL)			\
	  memory = (char *) malloc (allocated);				\
	else								\
	  memory = (char *) realloc (result, allocated);		\
									\
	if (memory == NULL)						\
	  {								\
	    if (!(result == resultbuf || result == NULL))		\
	      free (result);						\
	    freea (buf);						\
	    CLEANUP ();							\
	    errno = ENOMEM;						\
	    return NULL;						\
	  }								\
	if (result == resultbuf && length > 0)				\
	  memcpy (memory, result, length);				\
	result = memory;						\
      }

    for (cp = format, i = 0, dp = &d.dir[0];; cp = dp->dir_end, i++, dp++) {
      if (cp != dp->dir_start) {
        size_t n = dp->dir_start - cp;

        ENSURE_ALLOCATION (length + n);
        memcpy (result + length, cp, n);
        length += n;
      }
      if (i == d.count)
        break;

      /* Execute a single directive.  */
      if (dp->conversion == '%') {
        if (!(dp->arg_index < 0))
          abort ();
        ENSURE_ALLOCATION (length + 1);
        result[length] = '%';
        length += 1;
      } else {
        if (!(dp->arg_index >= 0))
          abort ();

        if (dp->conversion == 'n') {
          switch (a.arg[dp->arg_index].type) {
            case TYPE_COUNT_SCHAR_POINTER:
              *a.arg[dp->arg_index].a.a_count_schar_pointer = length;
              break;
            case TYPE_COUNT_SHORT_POINTER:
              *a.arg[dp->arg_index].a.a_count_short_pointer = length;
              break;
            case TYPE_COUNT_INT_POINTER:
              *a.arg[dp->arg_index].a.a_count_int_pointer = length;
              break;
            case TYPE_COUNT_LONGINT_POINTER:
              *a.arg[dp->arg_index].a.a_count_longint_pointer = length;
              break;
#ifdef HAVE_LONG_LONG
            case TYPE_COUNT_LONGLONGINT_POINTER:
              *a.arg[dp->arg_index].a.a_count_longlongint_pointer = length;
              break;
#endif
            default:
              abort ();
          }
        } else {
          arg_type type = a.arg[dp->arg_index].type;
          char *p;
          unsigned int prefix_count;
          int prefixes[2];
#ifndef HAVE_SNPRINTF
          unsigned int tmp_length;
          char tmpbuf[700];
          char *tmp;

          /* Allocate a temporary buffer of sufficient size for calling
             sprintf.  */
          {
            unsigned int width;
            unsigned int precision;

            width = 0;
            if (dp->width_start != dp->width_end) {
              if (dp->width_arg_index >= 0) {
                int arg;

                if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
                  abort ();
                arg = a.arg[dp->width_arg_index].a.a_int;
                width = (arg < 0 ? -arg : arg);
              } else {
                const char *digitp = dp->width_start;

                do
                  width = width * 10 + (*digitp++ - '0');
                while (digitp != dp->width_end);
              }
            }

            precision = 6;
            if (dp->precision_start != dp->precision_end) {
              if (dp->precision_arg_index >= 0) {
                int arg;

                if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
                  abort ();
                arg = a.arg[dp->precision_arg_index].a.a_int;
                precision = (arg < 0 ? 0 : arg);
              } else {
                const char *digitp = dp->precision_start + 1;

                precision = 0;
                while (digitp != dp->precision_end)
                  precision = precision * 10 + (*digitp++ - '0');
              }
            }

            switch (dp->conversion) {
              case 'd':
              case 'i':
              case 'u':
# ifdef HAVE_LONG_LONG
                if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.30103 /* binary -> decimal */
                      * 2       /* estimate for FLAG_GROUP */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                else
# endif
                if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.30103      /* binary -> decimal */
                      * 2       /* estimate for FLAG_GROUP */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                else
                  tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.30103       /* binary -> decimal */
                      * 2       /* estimate for FLAG_GROUP */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                break;

              case 'o':
# ifdef HAVE_LONG_LONG
                if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.333334        /* binary -> octal */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                else
# endif
                if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.333334     /* binary -> octal */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                else
                  tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.333334      /* binary -> octal */
                      )
                      + 1       /* turn floor into ceil */
                      + 1;      /* account for leading sign */
                break;

              case 'x':
              case 'X':
# ifdef HAVE_LONG_LONG
                if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long long) * CHAR_BIT * 0.25    /* binary -> hexadecimal */
                      )
                      + 1       /* turn floor into ceil */
                      + 2;      /* account for leading sign or alternate form */
                else
# endif
# ifdef HAVE_INT64_AND_I64
                if (type == TYPE_INT64 || type == TYPE_UINT64)
                  tmp_length = (unsigned int) (sizeof (unsigned __int64) * CHAR_BIT * 0.25      /* binary -> hexadecimal */
                      )
                      + 1       /* turn floor into ceil */
                      + 2;      /* account for leading sign or alternate form */
                else
# endif
                if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                  tmp_length = (unsigned int) (sizeof (unsigned long) * CHAR_BIT * 0.25 /* binary -> hexadecimal */
                      )
                      + 1       /* turn floor into ceil */
                      + 2;      /* account for leading sign or alternate form */
                else
                  tmp_length = (unsigned int) (sizeof (unsigned int) * CHAR_BIT * 0.25  /* binary -> hexadecimal */
                      )
                      + 1       /* turn floor into ceil */
                      + 2;      /* account for leading sign or alternate form */
                break;

              case 'f':
              case 'F':
# ifdef HAVE_LONG_DOUBLE
                if (type == TYPE_LONGDOUBLE)
                  tmp_length = (unsigned int) (LDBL_MAX_EXP * 0.30103   /* binary -> decimal */
                      * 2       /* estimate for FLAG_GROUP */
                      )
                      + 1       /* turn floor into ceil */
                      + precision + 10; /* sign, decimal point etc. */
                else
# endif
                  tmp_length = (unsigned int) (DBL_MAX_EXP * 0.30103    /* binary -> decimal */
                      * 2       /* estimate for FLAG_GROUP */
                      )
                      + 1       /* turn floor into ceil */
                      + precision + 10; /* sign, decimal point etc. */
                break;

              case 'e':
              case 'E':
              case 'g':
              case 'G':
              case 'a':
              case 'A':
                tmp_length = precision + 12;    /* sign, decimal point, exponent etc. */
                break;

              case 'c':
# ifdef HAVE_WINT_T
                if (type == TYPE_WIDE_CHAR)
                  tmp_length = MB_CUR_MAX;
                else
# endif
                  tmp_length = 1;
                break;

              case 's':
# ifdef HAVE_WCHAR_T
                if (type == TYPE_WIDE_STRING)
                  tmp_length = (a.arg[dp->arg_index].a.a_wide_string == NULL ? 6        /* wcslen(L"(null)") */
                      : local_wcslen (a.arg[dp->arg_index].a.a_wide_string))
                      * MB_CUR_MAX;
                else
# endif
                  tmp_length = a.arg[dp->arg_index].a.a_string == NULL ? 6      /* strlen("(null)") */
                      : strlen (a.arg[dp->arg_index].a.a_string);
                break;

              case 'p':
                tmp_length = (unsigned int) (sizeof (void *) * CHAR_BIT * 0.25  /* binary -> hexadecimal */
                    )
                    + 1         /* turn floor into ceil */
                    + 2;        /* account for leading 0x */

                /* make sure we always have enough space for a plain %p, so + */
                if (dp->flags & FLAG_PTR_EXT && a.arg[dp->arg_index].ext_string)
                  tmp_length += strlen (a.arg[dp->arg_index].ext_string);
                break;

              default:
                abort ();
            }

            if (tmp_length < width)
              tmp_length = width;

            tmp_length++;       /* account for trailing NUL */
          }

          if (tmp_length <= sizeof (tmpbuf))
            tmp = tmpbuf;
          else {
            tmp = (char *) malloc (tmp_length);
            if (tmp == NULL) {
              /* Out of memory.  */
              if (!(result == resultbuf || result == NULL))
                free (result);
              freea (buf);
              CLEANUP ();
              errno = ENOMEM;
              return NULL;
            }
          }
#endif

          /* Construct the format string for calling snprintf or
             sprintf.  */
          p = buf;
          *p++ = '%';
          if (dp->flags & FLAG_GROUP)
            *p++ = '\'';
          if (dp->flags & FLAG_LEFT)
            *p++ = '-';
          if (dp->flags & FLAG_SHOWSIGN)
            *p++ = '+';
          if (dp->flags & FLAG_SPACE)
            *p++ = ' ';
          if (dp->flags & FLAG_ALT)
            *p++ = '#';
          if (dp->flags & FLAG_ZERO)
            *p++ = '0';
          if (dp->width_start != dp->width_end) {
            size_t n = dp->width_end - dp->width_start;
            memcpy (p, dp->width_start, n);
            p += n;
          }
          if (dp->precision_start != dp->precision_end) {
            size_t n = dp->precision_end - dp->precision_start;
            memcpy (p, dp->precision_start, n);
            p += n;
          }

          switch (type) {
#ifdef HAVE_INT64_AND_I64
            case TYPE_INT64:
            case TYPE_UINT64:
              *p++ = 'I';
              *p++ = '6';
              *p++ = '4';
              break;
#endif
#ifdef HAVE_LONG_LONG
            case TYPE_LONGLONGINT:
            case TYPE_ULONGLONGINT:
#ifdef HAVE_INT64_AND_I64       /* The system (sn)printf uses %I64. Also assume
                                 * that long long == __int64.
                                 */
              *p++ = 'I';
              *p++ = '6';
              *p++ = '4';
              break;
#else
              *p++ = 'l';
               /*FALLTHROUGH*/
#endif
#endif
            case TYPE_LONGINT:
            case TYPE_ULONGINT:
#ifdef HAVE_WINT_T
            case TYPE_WIDE_CHAR:
#endif
#ifdef HAVE_WCHAR_T
            case TYPE_WIDE_STRING:
#endif
              *p++ = 'l';
              break;
#ifdef HAVE_LONG_DOUBLE
            case TYPE_LONGDOUBLE:
              *p++ = 'L';
              break;
#endif
            default:
              break;
          }
          *p = dp->conversion;
#ifdef HAVE_SNPRINTF
          p[1] = '%';
          p[2] = 'n';
          p[3] = '\0';
#else
          p[1] = '\0';
#endif

          /* Construct the arguments for calling snprintf or sprintf.  */
          prefix_count = 0;
          if (dp->width_arg_index >= 0) {
            if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
              abort ();
            prefixes[prefix_count++] = a.arg[dp->width_arg_index].a.a_int;
          }
          if (dp->precision_arg_index >= 0) {
            if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
              abort ();
            prefixes[prefix_count++] = a.arg[dp->precision_arg_index].a.a_int;
          }
#ifdef HAVE_SNPRINTF
          /* Prepare checking whether snprintf returns the count
             via %n.  */
          ENSURE_ALLOCATION (length + 1);
          result[length] = '\0';
#endif

          for (;;) {
            size_t maxlen;
            int count;
#ifdef HAVE_SNPRINTF
            int retcount;
#endif

            maxlen = allocated - length;
            count = -1;

#ifdef HAVE_SNPRINTF
            retcount = 0;

#define SNPRINTF_BUF(arg) \
		    switch (prefix_count)				    \
		      {							    \
		      case 0:						    \
			retcount = snprintf (result + length, maxlen, buf,  \
					     arg, &count);		    \
			break;						    \
		      case 1:						    \
			retcount = snprintf (result + length, maxlen, buf,  \
					     prefixes[0], arg, &count);	    \
			break;						    \
		      case 2:						    \
			retcount = snprintf (result + length, maxlen, buf,  \
					     prefixes[0], prefixes[1], arg, \
					     &count);			    \
			break;						    \
		      default:						    \
			abort ();					    \
		      }
#else
#define SNPRINTF_BUF(arg) \
		    switch (prefix_count)				    \
		      {							    \
		      case 0:						    \
			count = sprintf (tmp, buf, arg);		    \
			break;						    \
		      case 1:						    \
			count = sprintf (tmp, buf, prefixes[0], arg);	    \
			break;						    \
		      case 2:						    \
			count = sprintf (tmp, buf, prefixes[0], prefixes[1],\
					 arg);				    \
			break;						    \
		      default:						    \
			abort ();					    \
		      }
#endif

            switch (type) {
              case TYPE_SCHAR:
              {
                int arg = a.arg[dp->arg_index].a.a_schar;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_UCHAR:
              {
                unsigned int arg = a.arg[dp->arg_index].a.a_uchar;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_SHORT:
              {
                int arg = a.arg[dp->arg_index].a.a_short;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_USHORT:
              {
                unsigned int arg = a.arg[dp->arg_index].a.a_ushort;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_INT:
              {
                int arg = a.arg[dp->arg_index].a.a_int;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_UINT:
              {
                unsigned int arg = a.arg[dp->arg_index].a.a_uint;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_LONGINT:
              {
                long int arg = a.arg[dp->arg_index].a.a_longint;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_ULONGINT:
              {
                unsigned long int arg = a.arg[dp->arg_index].a.a_ulongint;
                SNPRINTF_BUF (arg);
              }
                break;
#ifdef HAVE_INT64_AND_I64
              case TYPE_INT64:
              {
                __int64 arg = a.arg[dp->arg_index].a.a_int64;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_UINT64:
              {
                unsigned __int64 arg = a.arg[dp->arg_index].a.a_uint64;
                SNPRINTF_BUF (arg);
              }
                break;
#endif
#ifdef HAVE_LONG_LONG
#ifndef HAVE_LONG_LONG_FORMAT
              case TYPE_LONGLONGINT:
              case TYPE_ULONGLONGINT:
              {
                unsigned long long int arg =
                    a.arg[dp->arg_index].a.a_ulonglongint;
                int width;
                int precision;

                width = 0;
                if (dp->width_start != dp->width_end) {
                  if (dp->width_arg_index >= 0) {
                    int arg;

                    if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
                      abort ();
                    arg = a.arg[dp->width_arg_index].a.a_int;
                    width = (arg < 0 ? -arg : arg);
                  } else {
                    const char *digitp = dp->width_start;

                    do
                      width = width * 10 + (*digitp++ - '0');
                    while (digitp != dp->width_end);
                  }
                }

                precision = -1;
                if (dp->precision_start != dp->precision_end) {
                  if (dp->precision_arg_index >= 0) {
                    int arg;

                    if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
                      abort ();
                    arg = a.arg[dp->precision_arg_index].a.a_int;
                    precision = (arg < 0 ? 0 : arg);
                  } else {
                    const char *digitp = dp->precision_start + 1;

                    precision = 0;
                    do
                      precision = precision * 10 + (*digitp++ - '0');
                    while (digitp != dp->precision_end);
                  }
                }
#ifdef HAVE_SNPRINTF
                count = print_long_long (result + length, maxlen,
                    width, precision, dp->flags, dp->conversion, arg);
#else
                count = print_long_long (tmp, tmp_length,
                    width, precision, dp->flags, dp->conversion, arg);
#endif
              }
                break;
#else
              case TYPE_LONGLONGINT:
              {
                long long int arg = a.arg[dp->arg_index].a.a_longlongint;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_ULONGLONGINT:
              {
                unsigned long long int arg =
                    a.arg[dp->arg_index].a.a_ulonglongint;
                SNPRINTF_BUF (arg);
              }
                break;
#endif
#endif
              case TYPE_DOUBLE:
              {
                double arg = a.arg[dp->arg_index].a.a_double;
                SNPRINTF_BUF (arg);
              }
                break;
#ifdef HAVE_LONG_DOUBLE
              case TYPE_LONGDOUBLE:
              {
                long double arg = a.arg[dp->arg_index].a.a_longdouble;
                SNPRINTF_BUF (arg);
              }
                break;
#endif
              case TYPE_CHAR:
              {
                int arg = a.arg[dp->arg_index].a.a_char;
                SNPRINTF_BUF (arg);
              }
                break;
#ifdef HAVE_WINT_T
              case TYPE_WIDE_CHAR:
              {
                wint_t arg = a.arg[dp->arg_index].a.a_wide_char;
                SNPRINTF_BUF (arg);
              }
                break;
#endif
              case TYPE_STRING:
              {
                const char *arg = a.arg[dp->arg_index].a.a_string == NULL
                    ? "(null)" : a.arg[dp->arg_index].a.a_string;
                SNPRINTF_BUF (arg);
              }
                break;
#ifdef HAVE_WCHAR_T
              case TYPE_WIDE_STRING:
              {
                const wchar_t *arg =
                    a.arg[dp->arg_index].a.a_wide_string ==
                    NULL ? L"(null)" : a.arg[dp->arg_index].a.a_wide_string;
                SNPRINTF_BUF (arg);
              }
                break;
#endif
              case TYPE_POINTER:
              {
                void *arg = a.arg[dp->arg_index].a.a_pointer;
                SNPRINTF_BUF (arg);
              }
                break;
              case TYPE_POINTER_EXT:
              {
                void *arg = a.arg[dp->arg_index].a.a_pointer;

                if (a.arg[dp->arg_index].ext_string != NULL) {
                  arg = a.arg[dp->arg_index].ext_string;
                  *p = 's';
                }

                SNPRINTF_BUF (arg);
              }
                break;
              default:
                abort ();
            }

#ifdef HAVE_SNPRINTF
            /* Portability: Not all implementations of snprintf()
               are ISO C 99 compliant.  Determine the number of
               bytes that snprintf() has produced or would have
               produced.  */
            if (count >= 0) {
              /* Verify that snprintf() has NUL-terminated its
                 result.  */
              if (count < maxlen && result[length + count] != '\0')
                abort ();
              /* Portability hack.  */
              if (retcount > count)
                count = retcount;
            } else {
              /* snprintf() doesn't understand the '%n'
                 directive.  */
              if (p[1] != '\0') {
                /* Don't use the '%n' directive; instead, look
                   at the snprintf() return value.  */
                p[1] = '\0';
                continue;
              }
              count = retcount;
            }
#endif

            /* Attempt to handle failure.  */
            if (count < 0) {
              if (!(result == resultbuf || result == NULL))
                free (result);
              freea (buf);
              CLEANUP ();
              errno = EINVAL;
              return NULL;
            }
#ifndef HAVE_SNPRINTF
            if (count >= tmp_length)
              /* tmp_length was incorrectly calculated - fix the
                 code above!  */
              abort ();
#endif

            /* Make room for the result.  */
            if (count >= maxlen) {
              /* Need at least count bytes.  But allocate
                 proportionally, to avoid looping eternally if
                 snprintf() reports a too small count.  */
              size_t n = length + count;

              if (n < 2 * allocated)
                n = 2 * allocated;

              ENSURE_ALLOCATION (n);
#ifdef HAVE_SNPRINTF
              continue;
#endif
            }
#ifdef HAVE_SNPRINTF
            /* The snprintf() result did fit.  */
#else
            /* Append the sprintf() result.  */
            memcpy (result + length, tmp, count);
            if (tmp != tmpbuf)
              free (tmp);
#endif

            length += count;
            break;
          }
        }
      }
    }

    /* Add the final NUL.  */
    ENSURE_ALLOCATION (length + 1);
    result[length] = '\0';

    if (result != resultbuf && length + 1 < allocated) {
      /* Shrink the allocated memory if possible.  */
      char *memory;

      memory = (char *) realloc (result, length + 1);
      if (memory != NULL)
        result = memory;
    }

    freea (buf);
    CLEANUP ();
    *lengthp = length;
    return result;
  }
}
