/* Formatted output to strings.
  Copyright (C) 1999-2000, 2002-2003, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  USA.  */

#include <config.h>

/* Specification.  */
#if WIDE_CHAR_VERSION
# include "wprintf-parse.h"
#else
# include "printf-parse.h"
#endif

/* Get size_t, NULL.  */
#include <stddef.h>

/* Get intmax_t.  */
#if HAVE_STDINT_H_WITH_UINTMAX
# include <stdint.h>
#endif
#if HAVE_INTTYPES_H_WITH_UINTMAX
# include <inttypes.h>
#endif

/* malloc(), realloc(), free().  */
#include <stdlib.h>

/* Checked size_t computations.  */
#include "xsize.h"

#if WIDE_CHAR_VERSION
# define PRINTF_PARSE wprintf_parse
# define CHAR_T wchar_t
# define DIRECTIVE wchar_t_directive
# define DIRECTIVES wchar_t_directives
#else
# define PRINTF_PARSE printf_parse
# define CHAR_T char
# define DIRECTIVE char_directive
# define DIRECTIVES char_directives
#endif

#ifdef STATIC
STATIC
#endif
int
PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a)
{
 const CHAR_T *cp = format;            /* pointer into format */
 size_t arg_posn = 0;          /* number of regular arguments consumed */
 size_t d_allocated;                   /* allocated elements of d->dir */
 size_t a_allocated;                   /* allocated elements of a->arg */
 size_t max_width_length = 0;
 size_t max_precision_length = 0;

 d->count = 0;
 d_allocated = 1;
 d->dir = malloc (d_allocated * sizeof (DIRECTIVE));
 if (d->dir == NULL)
   /* Out of memory.  */
   return -1;

 a->count = 0;
 a_allocated = 0;
 a->arg = NULL;

#define REGISTER_ARG(_index_,_type_) \
 {                                                                     \
   size_t n = (_index_);                                               \
   if (n >= a_allocated)                                               \
     {                                                                 \
       size_t memory_size;                                             \
       argument *memory;                                               \
                                                                       \
       a_allocated = xtimes (a_allocated, 2);                          \
       if (a_allocated <= n)                                           \
         a_allocated = xsum (n, 1);                                    \
       memory_size = xtimes (a_allocated, sizeof (argument));          \
       if (size_overflow_p (memory_size))                              \
         /* Overflow, would lead to out of memory.  */                 \
         goto error;                                                   \
       memory = (a->arg                                                \
                 ? realloc (a->arg, memory_size)                       \
                 : malloc (memory_size));                              \
       if (memory == NULL)                                             \
         /* Out of memory.  */                                         \
         goto error;                                                   \
       a->arg = memory;                                                \
     }                                                                 \
   while (a->count <= n)                                               \
     a->arg[a->count++].type = TYPE_NONE;                              \
   if (a->arg[n].type == TYPE_NONE)                                    \
     a->arg[n].type = (_type_);                                        \
   else if (a->arg[n].type != (_type_))                                \
     /* Ambiguous type for positional argument.  */                    \
     goto error;                                                       \
 }

 while (*cp != '\0')
   {
     CHAR_T c = *cp++;
     if (c == '%')
       {
         size_t arg_index = ARG_NONE;
         DIRECTIVE *dp = &d->dir[d->count];/* pointer to next directive */

         /* Initialize the next directive.  */
         dp->dir_start = cp - 1;
         dp->flags = 0;
         dp->width_start = NULL;
         dp->width_end = NULL;
         dp->width_arg_index = ARG_NONE;
         dp->precision_start = NULL;
         dp->precision_end = NULL;
         dp->precision_arg_index = ARG_NONE;
         dp->arg_index = ARG_NONE;

         /* Test for positional argument.  */
         if (*cp >= '0' && *cp <= '9')
           {
             const CHAR_T *np;

             for (np = cp; *np >= '0' && *np <= '9'; np++)
               ;
             if (*np == '$')
               {
                 size_t n = 0;

                 for (np = cp; *np >= '0' && *np <= '9'; np++)
                   n = xsum (xtimes (n, 10), *np - '0');
                 if (n == 0)
                   /* Positional argument 0.  */
                   goto error;
                 if (size_overflow_p (n))
                   /* n too large, would lead to out of memory later.  */
                   goto error;
                 arg_index = n - 1;
                 cp = np + 1;
               }
           }

         /* Read the flags.  */
         for (;;)
           {
             if (*cp == '\'')
               {
                 dp->flags |= FLAG_GROUP;
                 cp++;
               }
             else if (*cp == '-')
               {
                 dp->flags |= FLAG_LEFT;
                 cp++;
               }
             else if (*cp == '+')
               {
                 dp->flags |= FLAG_SHOWSIGN;
                 cp++;
               }
             else if (*cp == ' ')
               {
                 dp->flags |= FLAG_SPACE;
                 cp++;
               }
             else if (*cp == '#')
               {
                 dp->flags |= FLAG_ALT;
                 cp++;
               }
             else if (*cp == '0')
               {
                 dp->flags |= FLAG_ZERO;
                 cp++;
               }
             else
               break;
           }

         /* Parse the field width.  */
         if (*cp == '*')
           {
             dp->width_start = cp;
             cp++;
             dp->width_end = cp;
             if (max_width_length < 1)
               max_width_length = 1;

             /* Test for positional argument.  */
             if (*cp >= '0' && *cp <= '9')
               {
                 const CHAR_T *np;

                 for (np = cp; *np >= '0' && *np <= '9'; np++)
                   ;
                 if (*np == '$')
                   {
                     size_t n = 0;

                     for (np = cp; *np >= '0' && *np <= '9'; np++)
                       n = xsum (xtimes (n, 10), *np - '0');
                     if (n == 0)
                       /* Positional argument 0.  */
                       goto error;
                     if (size_overflow_p (n))
                       /* n too large, would lead to out of memory later.  */
                       goto error;
                     dp->width_arg_index = n - 1;
                     cp = np + 1;
                   }
               }
             if (dp->width_arg_index == ARG_NONE)
               {
                 dp->width_arg_index = arg_posn++;
                 if (dp->width_arg_index == ARG_NONE)
                   /* arg_posn wrapped around.  */
                   goto error;
               }
             REGISTER_ARG (dp->width_arg_index, TYPE_INT);
           }
         else if (*cp >= '0' && *cp <= '9')
           {
             size_t width_length;

             dp->width_start = cp;
             for (; *cp >= '0' && *cp <= '9'; cp++)
               ;
             dp->width_end = cp;
             width_length = dp->width_end - dp->width_start;
             if (max_width_length < width_length)
               max_width_length = width_length;
           }

         /* Parse the precision.  */
         if (*cp == '.')
           {
             cp++;
             if (*cp == '*')
               {
                 dp->precision_start = cp - 1;
                 cp++;
                 dp->precision_end = cp;
                 if (max_precision_length < 2)
                   max_precision_length = 2;

                 /* Test for positional argument.  */
                 if (*cp >= '0' && *cp <= '9')
                   {
                     const CHAR_T *np;

                     for (np = cp; *np >= '0' && *np <= '9'; np++)
                       ;
                     if (*np == '$')
                       {
                         size_t n = 0;

                         for (np = cp; *np >= '0' && *np <= '9'; np++)
                           n = xsum (xtimes (n, 10), *np - '0');
                         if (n == 0)
                           /* Positional argument 0.  */
                           goto error;
                         if (size_overflow_p (n))
                           /* n too large, would lead to out of memory
                              later.  */
                           goto error;
                         dp->precision_arg_index = n - 1;
                         cp = np + 1;
                       }
                   }
                 if (dp->precision_arg_index == ARG_NONE)
                   {
                     dp->precision_arg_index = arg_posn++;
                     if (dp->precision_arg_index == ARG_NONE)
                       /* arg_posn wrapped around.  */
                       goto error;
                   }
                 REGISTER_ARG (dp->precision_arg_index, TYPE_INT);
               }
             else
               {
                 size_t precision_length;

                 dp->precision_start = cp - 1;
                 for (; *cp >= '0' && *cp <= '9'; cp++)
                   ;
                 dp->precision_end = cp;
                 precision_length = dp->precision_end - dp->precision_start;
                 if (max_precision_length < precision_length)
                   max_precision_length = precision_length;
               }
           }

         {
           arg_type type;

           /* Parse argument type/size specifiers.  */
           {
             int flags = 0;

             for (;;)
               {
                 if (*cp == 'h')
                   {
                     flags |= (1 << (flags & 1));
                     cp++;
                   }
                 else if (*cp == 'L')
                   {
                     flags |= 4;
                     cp++;
                   }
                 else if (*cp == 'l')
                   {
                     flags += 8;
                     cp++;
                   }
#ifdef HAVE_INTMAX_T
                 else if (*cp == 'j')
                   {
                     if (sizeof (intmax_t) > sizeof (long))
                       {
                         /* intmax_t = long long */
                         flags += 16;
                       }
                     else if (sizeof (intmax_t) > sizeof (int))
                       {
                         /* intmax_t = long */
                         flags += 8;
                       }
                     cp++;
                   }
#endif
                 else if (*cp == 'z' || *cp == 'Z')
                   {
                     /* 'z' is standardized in ISO C 99, but glibc uses 'Z'
                        because the warning facility in gcc-2.95.2 understands
                        only 'Z' (see gcc-2.95.2/gcc/c-common.c:1784).  */
                     if (sizeof (size_t) > sizeof (long))
                       {
                         /* size_t = long long */
                         flags += 16;
                       }
                     else if (sizeof (size_t) > sizeof (int))
                       {
                         /* size_t = long */
                         flags += 8;
                       }
                     cp++;
                   }
                 else if (*cp == 't')
                   {
                     if (sizeof (ptrdiff_t) > sizeof (long))
                       {
                         /* ptrdiff_t = long long */
                         flags += 16;
                       }
                     else if (sizeof (ptrdiff_t) > sizeof (int))
                       {
                         /* ptrdiff_t = long */
                         flags += 8;
                       }
                     cp++;
                   }
                 else
                   break;
               }

             /* Read the conversion character.  */
             c = *cp++;
             switch (c)
               {
               case 'd': case 'i':
#ifdef HAVE_LONG_LONG_INT
                 /* If 'long long' exists and is larger than 'long':  */
                 if (flags >= 16 || (flags & 4))
                   type = TYPE_LONGLONGINT;
                 else
#endif
                 /* If 'long long' exists and is the same as 'long', we parse
                    "lld" into TYPE_LONGINT.  */
                 if (flags >= 8)
                   type = TYPE_LONGINT;
                 else if (flags & 2)
                   type = TYPE_SCHAR;
                 else if (flags & 1)
                   type = TYPE_SHORT;
                 else
                   type = TYPE_INT;
                 break;
               case 'o': case 'u': case 'x': case 'X':
#ifdef HAVE_LONG_LONG_INT
                 /* If 'long long' exists and is larger than 'long':  */
                 if (flags >= 16 || (flags & 4))
                   type = TYPE_ULONGLONGINT;
                 else
#endif
                 /* If 'unsigned long long' exists and is the same as
                    'unsigned long', we parse "llu" into TYPE_ULONGINT.  */
                 if (flags >= 8)
                   type = TYPE_ULONGINT;
                 else if (flags & 2)
                   type = TYPE_UCHAR;
                 else if (flags & 1)
                   type = TYPE_USHORT;
                 else
                   type = TYPE_UINT;
                 break;
               case 'f': case 'F': case 'e': case 'E': case 'g': case 'G':
               case 'a': case 'A':
#ifdef HAVE_LONG_DOUBLE
                 if (flags >= 16 || (flags & 4))
                   type = TYPE_LONGDOUBLE;
                 else
#endif
                 type = TYPE_DOUBLE;
                 break;
               case 'c':
                 if (flags >= 8)
#ifdef HAVE_WINT_T
                   type = TYPE_WIDE_CHAR;
#else
                   goto error;
#endif
                 else
                   type = TYPE_CHAR;
                 break;
#ifdef HAVE_WINT_T
               case 'C':
                 type = TYPE_WIDE_CHAR;
                 c = 'c';
                 break;
#endif
               case 's':
                 if (flags >= 8)
#ifdef HAVE_WCHAR_T
                   type = TYPE_WIDE_STRING;
#else
                   goto error;
#endif
                 else
                   type = TYPE_STRING;
                 break;
#ifdef HAVE_WCHAR_T
               case 'S':
                 type = TYPE_WIDE_STRING;
                 c = 's';
                 break;
#endif
               case 'p':
                 type = TYPE_POINTER;
                 break;
               case 'n':
#ifdef HAVE_LONG_LONG_INT
                 /* If 'long long' exists and is larger than 'long':  */
                 if (flags >= 16 || (flags & 4))
                   type = TYPE_COUNT_LONGLONGINT_POINTER;
                 else
#endif
                 /* If 'long long' exists and is the same as 'long', we parse
                    "lln" into TYPE_COUNT_LONGINT_POINTER.  */
                 if (flags >= 8)
                   type = TYPE_COUNT_LONGINT_POINTER;
                 else if (flags & 2)
                   type = TYPE_COUNT_SCHAR_POINTER;
                 else if (flags & 1)
                   type = TYPE_COUNT_SHORT_POINTER;
                 else
                   type = TYPE_COUNT_INT_POINTER;
                 break;
               case '%':
                 type = TYPE_NONE;
                 break;
               default:
                 /* Unknown conversion character.  */
                 goto error;
               }
           }

           if (type != TYPE_NONE)
             {
               dp->arg_index = arg_index;
               if (dp->arg_index == ARG_NONE)
                 {
                   dp->arg_index = arg_posn++;
                   if (dp->arg_index == ARG_NONE)
                     /* arg_posn wrapped around.  */
                     goto error;
                 }
               REGISTER_ARG (dp->arg_index, type);
             }
           dp->conversion = c;
           dp->dir_end = cp;
         }

         d->count++;
         if (d->count >= d_allocated)
           {
             size_t memory_size;
             DIRECTIVE *memory;

             d_allocated = xtimes (d_allocated, 2);
             memory_size = xtimes (d_allocated, sizeof (DIRECTIVE));
             if (size_overflow_p (memory_size))
               /* Overflow, would lead to out of memory.  */
               goto error;
             memory = realloc (d->dir, memory_size);
             if (memory == NULL)
               /* Out of memory.  */
               goto error;
             d->dir = memory;
           }
       }
   }
 d->dir[d->count].dir_start = cp;

 d->max_width_length = max_width_length;
 d->max_precision_length = max_precision_length;
 return 0;

error:
 if (a->arg)
   free (a->arg);
 if (d->dir)
   free (d->dir);
 return -1;
}

#undef DIRECTIVES
#undef DIRECTIVE
#undef CHAR_T
#undef PRINTF_PARSE