/* scanf0.c (emx+gcc) */

/* Note: This program is not portable and uses dirty tricks. */
/*       It's a test program, not a sample program.          */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define FALSE 0
#define TRUE  1

#define TYPE_STRING  0
#define TYPE_U16     1
#define TYPE_S16     2
#define TYPE_U32     3
#define TYPE_S32     4
#define TYPE_U64     5
#define TYPE_S64     6
#define TYPE_FLOAT   7
#define TYPE_DOUBLE  8
#define TYPE_LDOUBLE 9

struct data
{
  char type;
  size_t len;
};

static struct data *data;
static void **list;
static int allocated, used;


static void add (size_t n, char type)
{
  void *tmp;

  tmp = malloc (n);
  if (tmp == NULL)
    {
      fputs ("Out of memory\n", stderr);
      exit (1);
    }
  if (used >= allocated)
    {
      allocated += 16;
      list = realloc (list, allocated * sizeof (*list));
      data = realloc (data, allocated * sizeof (*data));
      if (list == NULL || data == NULL)
        {
          fputs ("Out of memory\n", stderr);
          exit (1);
        }
    }
  list[used] = tmp;
  data[used].type = type;
  data[used].len = n;
  ++used;
}


static void parse (const char *fmt)
{
  int assign, first;
  char size;

  list = NULL; data = NULL; allocated = 0; used = 0;
  while (*fmt != 0)
    {
      if (*fmt == '%')
        {
          ++fmt;
          assign = TRUE; size = 0;
          if (*fmt == '*')
            {
              assign = FALSE; ++fmt;
            }
          while (isdigit ((unsigned char)*fmt))
            ++fmt;
          if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L')
            size = *fmt++;
          if (*fmt == 0)
            break;
          switch (*fmt)
            {
            case '[':
              first = TRUE;
              ++fmt;
              if (*fmt == '^')
                ++fmt;
              while (*fmt != 0)
                {
                  if (*fmt == ']' && !first)
                    break;
                  if (fmt[1] == '-' && fmt[2] != 0 &&
                      (unsigned char)fmt[0] < (unsigned char)fmt[2])
                    fmt += 3;
                  else
                    ++fmt;
                  first = FALSE;
                }
              if (*fmt == 0)
                --fmt;
              if (assign)
                add (1024, TYPE_STRING);
              break;
            case 'c':
            case 's':
              if (assign)
                add (1024, TYPE_STRING);
              break;
            case 'f':
            case 'e':
            case 'E':
            case 'g':
            case 'G':
              if (assign)
                switch (size)
                  {
                  case 'L':
                    add (sizeof (long double), TYPE_LDOUBLE);
                    break;
                  case 'l':
                    add (sizeof (double), TYPE_DOUBLE);
                    break;
                  default:
                    add (sizeof (float), TYPE_FLOAT);
                    break;
                  }
              break;
            case 'i':
            case 'd':
            case 'n':
              if (assign)
                switch (size)
                  {
                  case 'L':
                    add (sizeof (long long), TYPE_S64);
                    break;
                  case 'h':
                    add (sizeof (short), TYPE_S16);
                    break;
                  default:
                    add (sizeof (int), TYPE_S32);
                }
              break;
            case 'u':
            case 'o':
            case 'x':
            case 'X':
            case 'p':
              if (assign)
                switch (size)
                  {
                  case 'L':
                    add (sizeof (unsigned long long), TYPE_U64);
                    break;
                  case 'h':
                    add (sizeof (unsigned short), TYPE_U16);
                    break;
                  default:
                    add (sizeof (unsigned), TYPE_U32);
                    break;
                  }
              break;
            }
        }
      ++fmt;
    }
}


int main (int argc, char *argv[])
{
  int i, n;
  char fmt[128], *p, str_flag;

  str_flag = 0;
  if (argc == 2 && strcmp (argv[1], "-s") == 0)
    str_flag = 1;
  else if (argc != 1)
    {
      fputs ("Usage: scanf0 [-s]\n", stderr);
      exit (1);
    }

  printf ("Format: "); fflush (stdout);
  if (fgets (fmt, sizeof (fmt), stdin) == NULL)
    return 0;
  p = strchr (fmt, '\n');
  if (p != NULL) *p = 0;
  parse (fmt);
  for (;;)
    {
      for (i = 0; i < used; ++i)
        memset (list[i], 0, data[i].len);
      printf ("Input: "); fflush (stdout);
      if (str_flag)
        {
          char inp[512];

          if (fgets (inp, sizeof (inp), stdin) == NULL)
            return 0;
          p = strchr (inp, '\n');
          if (p != NULL) *p = 0;
          /* NOTE: THIS USAGE OF vscanf() IS ILLEGAL!  This is test code! */
          n = vsscanf (inp, fmt, (va_list)list);
        }
      else
        {
          /* NOTE: THIS USAGE OF vscanf() IS ILLEGAL!  This is test code! */
          n = vscanf (fmt, (va_list)list);
        }
      printf ("n=%d\n", n);
      for (i = 0; i < used; ++i)
        {
          printf ("%d:", i+1);
          switch (data[i].type)
            {
            case TYPE_STRING:
              printf ("%s", (char *)list[i]);
              break;
            case TYPE_U16:
              printf ("%hu", *(unsigned short *)list[i]);
              break;
            case TYPE_S16:
              printf ("%hd", *(short *)list[i]);
              break;
            case TYPE_U32:
              printf ("%lu", *(unsigned long *)list[i]);
              break;
            case TYPE_S32:
              printf ("%ld", *(long *)list[i]);
              break;
            case TYPE_U64:
              printf ("%Lu", *(unsigned long long *)list[i]);
              break;
            case TYPE_S64:
              printf ("%Ld", *(long long *)list[i]);
              break;
            case TYPE_FLOAT:
              printf ("%g", *(float *)list[i]);
              break;
            case TYPE_DOUBLE:
              printf ("%g", *(double *)list[i]);
              break;
            case TYPE_LDOUBLE:
              printf ("%Lg", *(long double *)list[i]);
              break;
          }
          putchar ('\n');
        }
      if (feof (stdin))
        break;
    }
  return 0;
}
