/* input.c (emx+gcc) -- Copyright (c) 1990-1994 by Eberhard Mattes */

#include <sys/emx.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <limits.h>

#define JEOF       1     /* longjmp: EOF reached */
#define JDONE      2     /* longjmp: input doesn't match */

/* This structure holds the local variables of _input() which are
   passed to the functions called by _input(). */

typedef struct
{
  FILE *stream;                 /* Where input comes from */
  int width;                    /* Field width or what's left over of it */
  int chars;                    /* Number of characters read */
  int volatile count;           /* Number of fields assigned */
  char * volatile more;         /* Big buffer for %e format */
  int collected;                /* Number of characters collected */
  size_t more_size;             /* Size of the above */
  char size;                    /* Size (0, 'h', 'l' or 'L') */
  char at_eof;                  /* End of file or string reached */
  jmp_buf jump;                 /* Jump there when done or on error */
} ilocal;


static unsigned char next (ilocal *v)
{
  int c;

  c = getc (v->stream);
  if (c == EOF)
    longjmp (v->jump, JEOF);
  ++v->chars;
  return ((unsigned char)c);
}


static int get (ilocal *v, unsigned char *pc)
{
  int c;

  c = getc (v->stream);
  if (c == EOF)
    {
      v->at_eof = 1;
      return (0);
    }
  *pc = (unsigned char)c;
  ++v->chars;
  return (1);
}


static void push_back (ilocal *v, unsigned char c)
{
  ungetc (c, v->stream);
  --v->chars;
}


#define COLLECT(X) collect (v, buf, sizeof (buf), X)

static void collect (ilocal *v, unsigned char *buf, size_t buf_size,
                     unsigned char c)
{
  if (v->more == NULL && v->collected < buf_size)
    buf[v->collected] = c;
  else
    {
      if (v->collected >= v->more_size)
        {
          v->more_size += 512;
          v->more = realloc (v->more, v->more_size);
          if (v->more == NULL)
            longjmp (v->jump, JDONE);
          if (v->collected == buf_size)
            memcpy (v->more, buf, buf_size);
        }
      v->more[v->collected] = c;
    }
  ++v->collected;
}


static unsigned char skip (ilocal *v)
{
  unsigned char c;

  do
    {
      c = next (v);
    } while (isspace (c));
  return (c);
}


static void inp_char (ilocal *v, unsigned char *dst)
{
  unsigned char c;
  int width;

  width = ((v->width == INT_MAX) ? 1 : v->width);
  while (width > 0)
    {
      --width;
      c = next (v);
      if (dst != NULL)
        {
          *dst++ = c;
          ++v->count;
        }
    }
}


static void inp_string (ilocal *v, unsigned char c, char *map, char end,
                        unsigned char *dst)
{
  while (v->width > 0 && map[c] != end)
    {
      --v->width;
      if (dst != NULL)
        *dst++ = c;
      if (!get (v, &c))
        break;
    }
  if (dst != NULL)
    {
      *dst = 0;
      ++v->count;
    }
  if (v->at_eof)
    longjmp (v->jump, JEOF);
  push_back (v, c);
}


static void inp_str (ilocal *v, unsigned char *dst)
{
  char map[256];
  unsigned char c;

  memset (map, 0, 256);
  map[' '] = 1;
  map['\n'] = 1;
  map['\t'] = 1;
  c = skip (v);
  inp_string (v, c, map, 1, dst);
}


static void inp_set (ilocal *v, const char **pfmt, void *dst)
{
  char map[256], end, done;
  unsigned char f;
  const char *format;
  int i;

  format = *pfmt;
  memset (map, 0, 256);
  end = 0;
  ++format;
  if (*format == '^')
    {
      ++format; end = 1;
    }
  i = 0; done = 0;
  do
    {
      f = (unsigned char)*format;
      switch (f)
        {
        case 0:
          *pfmt = format - 1;   /* Avoid skipping past 0 */
          done = 1;
          break;
        case ']':
          if (i > 0)
            {
              *pfmt = format;
              done = 1;
              break;
            }
          /* no break */
        default:
          if (format[1] == '-' && format[2] != 0 &&
              f < (unsigned char)format[2])
            {
              memset (map+f, 1, (unsigned char)format[2]-f);
              format += 2;
            }
          else
            map[f] = 1;
          break;
        }
      ++format; ++i;
    } while (!done);
  inp_string (v, next (v), map, end, dst);
}


static void inp_int_base (ilocal *v, void *dst, unsigned char c, int base)
{
  char neg, ok;
  unsigned long long n;
  int digit;

  neg = FALSE;
  if (v->width > 0)
    {
      if (c == '+')
        {
          c = next (v); --v->width;
        }
      else if (c == '-')
        {
          neg = TRUE;
          c = next (v); --v->width;
        }
    }

  n = 0; ok = FALSE;

  if (base == 0)
    {
      base = 10;
      if (v->width > 0 && c == '0')
        {
          c = next (v); --v->width;
          if (v->width > 0 && (c == 'x' || c == 'X'))
            {
              base = 16;
              c = next (v); --v->width;
            }
          else
            {
              base = 8;
              ok = TRUE; /* We've seen a digit! */
            }
        }
    }
  else if (base == 16 && v->width > 0 && c == '0')
    {
      c = next (v); --v->width;
      if (v->width > 0 && (c == 'x' || c == 'X'))
        {
          c = next (v); --v->width;
        }
      else
        ok = TRUE;          /* We've seen a digit! */
    }

  while (v->width > 0)
    {
      --v->width;
      if (isdigit (c))
        digit = c - '0';
      else if (isupper (c))
        digit = c - 'A' + 10;
      else if (islower (c))
        digit = c - 'a' + 10;
      else
        break;
      if (digit < 0 || digit >= base)
        break;
      ok = TRUE;
      n = n * base + digit;
      if (!get (v, &c))
        break;
    }
  if (!ok)
    longjmp (v->jump, JDONE);
  if (neg)
    n = -n;
  if (dst != NULL)
    {
      switch (v->size)
        {
        case 'L':
          *(long long *)dst = n;
          break;
        case 'h':
          *(short *)dst = n;
          break;
        default:
          *(int *)dst = n;
          break;
        }
      ++v->count;
    }
  if (v->at_eof)
    longjmp (v->jump, JDONE);
  push_back (v, c);
}


static void inp_int (ilocal *v, unsigned char f, void *dst)
{
  unsigned char c;

  c = skip (v);
  switch (f)
    {
    case 'i':
      inp_int_base (v, dst, c, 0);
      break;

    case 'd':
      inp_int_base (v, dst, c, 10);
      break;

    case 'u':
      inp_int_base (v, dst, c, 10);
      break;

    case 'o':
      inp_int_base (v, dst, c, 8);
      break;

    case 'x':
    case 'X':
    case 'p':
      inp_int_base (v, dst, c, 16);
      break;

    default:
      abort ();
    }
}


static void inp_float (ilocal *v, void *dst)
{
  unsigned char c;
  char *q;
  int width;
  char ok;
  char buf[128];
  long double x;

  v->collected = 0;
  width = v->width;
  c = skip (v);
  ok = FALSE;
  if (c == '+' || c == '-')
    {
      COLLECT (c);
      c = next (v); --width;
    }
  while (width > 0 && isdigit (c))
    {
      --width;
      COLLECT (c);
      ok = TRUE;
      if (!get (v, &c))
        break;
    }
  if (width > 0 && !v->at_eof && c == '.')
    {
      --width;
      COLLECT (c);
      while (get (v, &c) && width > 0 && isdigit (c))
        {
          --width;
          COLLECT (c);
          ok = TRUE;
        }
    }
  if (!ok)
    longjmp (v->jump, JDONE);

  if (width > 0 && !v->at_eof && (c == 'e' || c == 'E'))
    {
      COLLECT (c);
      c = next (v); --width;
      if (width > 0 && (c == '+' || c == '-'))
        {
          COLLECT (c);
          c = next (v); --width;
        }
      if (!(width > 0 && isdigit (c)))
        {
          push_back (v, c);
          longjmp (v->jump, JDONE);
        }
      while (width > 0 && isdigit (c))
        {
          --width;
          COLLECT (c);
          if (!get (v, &c))
            break;
        }
    }
  if (!v->at_eof)
    push_back (v, c);
  COLLECT (0);

  x = _strtold (((v->more != NULL) ? v->more : buf), &q);
  if (q == buf || *q != 0) /* Ignore overflow! */
    longjmp (v->jump, JDONE);
  if (dst != NULL)
    {
      switch (v->size)
        {
        case 'L':
          *(long double *)dst = x;
          break;
        case 'l':
          *(double *)dst = x;
          break;
        default:
          *(float *)dst = x;
          break;
        }
      ++v->count;
    }
  if (v->at_eof)
    longjmp (v->jump, JEOF);
}


static int inp_main (ilocal *v, const char *format, char *arg_ptr)
{
  void *dst;
  unsigned char f, c;
  char assign;

  c = 0;
  while ((f = *format) != 0)
    {
      if (isspace (f))
        {
          do
            {
              ++format; f = *format;
            } while (isspace (f));
          do
            {
              if (!get (v, &c))
                {
                  if (f == 0 || v->count != 0)
                    return (v->count);
                  else
                    return (EOF);
                }
            } while (isspace (c));
          push_back (v, c);
        }
      else if (f != '%')
        {
          c = next (v);
          if (c != f)
            {
              push_back (v, c);
              return (v->count);
            }
          ++format;
        }
      else
        {
          v->size = 0; v->width = INT_MAX; assign = TRUE; dst = NULL;
          ++format;
          if (*format == '*')
            {
              assign = FALSE;
              ++format;
            }
          if (isdigit (*format))
            {
              v->width = 0;
              while (isdigit (*format))
                v->width = v->width * 10 + (*format++ - '0');
              if (v->width == 0)
                v->width = INT_MAX;
            }
          if (*format == 'h' || *format == 'l' || *format == 'L')
            v->size = *format++;
          f = *format;
          switch (f)
            {
            case 'c':
              if (assign)
                dst = va_arg (arg_ptr, char *);
              inp_char (v, dst);
              break;

            case '[':
              if (assign)
                dst = va_arg (arg_ptr, char *);
              inp_set (v, &format, dst);
              break;

            case 's':
              if (assign)
                dst = va_arg (arg_ptr, char *);
              inp_str (v, dst);
              break;

            case 'f':
            case 'e':
            case 'E':
            case 'g':
            case 'G':
              if (assign)
                switch (v->size)
                  {
                  case 'L':
                    dst = va_arg (arg_ptr, long double *);
                    break;
                  case 'l':
                      dst = va_arg (arg_ptr, double *);
                    break;
                  default:
                    dst = va_arg (arg_ptr, float *);
                    break;
                  }
              inp_float (v, dst);
              break;

            case 'i':
            case 'd':
            case 'u':
            case 'o':
            case 'x':
            case 'X':
            case 'p':
              if (assign)
                switch (v->size)
                  {
                  case 'L':
                    dst = va_arg (arg_ptr, long long *);
                    break;
                  case 'h':
                    dst = va_arg (arg_ptr, short *);
                    break;
                  default:
                    dst = va_arg (arg_ptr, int *);
                    break;
                  }
              inp_int (v, f, dst);
              break;

            case 'n':
              if (assign)
                switch (v->size)
                  {
                  case 'L':
                    *(va_arg (arg_ptr, long long *)) = v->chars;
                    break;
                  case 'h':
                    *(va_arg (arg_ptr, short *)) = v->chars;
                    break;
                  default:
                    *(va_arg (arg_ptr, int *)) = v->chars;
                    break;
                  }
              break;

            default:
              if (f == 0)                 /* % at end of string */
                return (v->count);
              c = next (v);
              if (c != f)
                return (v->count);
              break;
            }
          ++format;
        }
    }
  return (v->count);
}


int _input (FILE *stream, const char *format, char *arg_ptr)
{
  ilocal v;
  int rc;

  v.stream = stream;
  v.more = NULL; v.more_size = 0;
  v.count = 0; v.chars = 0; v.at_eof = 0;
  switch (setjmp (v.jump))
    {
    case JEOF:
      /* End of file (or string) reached.  If no fields were assigned,
         return EOF.  Otherwise return the number of fields
         assigned. */

      rc = ((v.count == 0) ? EOF : v.count);
      break;

    case JDONE:
      /* Stop conversion due to non-matching character.  Return the
         number of fields assigned. */

      rc = v.count;
      break;

    default:
      /* This is the main line. */

      rc = inp_main (&v, format, arg_ptr);
      break;
    }

  if (v.more != NULL)
    free (v.more);
  return (rc);
}
