/* Copyright (C) 1991 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   
   The GNU C Library 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 of the
   License, or (at your option) any later version.
   
   The GNU C Library 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 the GNU C Library; see the file COPYING.LIB.  If
   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
   Cambridge, MA 02139, USA.  */

/* 
 * adapted for atariST gcc lib
 *
 * NOTES: interface is different from equivalent gnuC lib function
 *        it was munged to match our old _scanf().
 *
 *	  if __NO_FLOAT__ is defined then the floating point stuff
 *	  gets nuked (for iio*.olb) as per our old scanf.c.
 *
 *  It is very important to read and understand the GNU Library General 
 *  Public License. It specifies rights and conditions that are different
 *  from the GNU copyleft.
 *
 *    ++jrb bammi@cadence.com
 */

#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* the code assumes this definition, note: traditional <ctype> def
 * of tolower will break the code. Ansi def should be ok.
 */
#ifdef tolower
#  undef tolower
#  define	tolower(c)	(isupper(c) ? (c)^0x20 : (c))
#endif

#ifndef __NO_FLOAT__
#  define FLOATS 1
#else
#  define FLOATS 0
#endif

#define TEN_MUL(X)	((((X) << 2) + (X)) << 1)

#ifdef	__GNUC__
#define	HAVE_LONGLONG
#define	LONGLONG	long long
#else
#define	LONGLONG	long
#endif


#define	inchar()	((c = ((*get)(s))) == EOF ? EOF : (++read_in, c))
#define unchar(c)	(--read_in, (*unget)(c, s))
#define	conv_error()	return((c == EOF || ((*unget)(c, s))), done)
#define input_error()	return( done < 1 ? EOF : done )
#define	memory_error()	return((errno = ENOMEM), EOF)


/* Read formatted input from S according to the format string
   FORMAT, using the argument list in ARG.
   Return the number of assignments made, or -1 for an input error.  */
int _scanf(s, get, unget, format, arg)
register FILE *s;
int (*get) __PROTO((FILE *));
int (*unget) __PROTO((int, FILE *));
const char *format;
va_list arg;
{
    register const char *f = format;
    register char fc;		/* Current character of the format.  */
    register size_t done = 0;	/* Assignments done.  */
    register size_t read_in = 0;	/* Chars read in.  */
    register int c;		/* Last char read.  */
    register int do_assign;	/* Whether to do an assignment.  */
    register int width;		/* Maximum field width.  */
    
    /* Type modifiers.  */
    char is_short, is_long, is_long_double;
#ifdef	HAVE_LONGLONG
    /* We use the `L' modifier for `long long int'.  */
#define	is_longlong	is_long_double
#else
#define	is_longlong	0
#endif
#if FLOATS
    /* Status for reading F-P nums.  */
    char got_dot, got_e;
#endif
    /* If a [...] is a [^...].  */
    char not_in;
    /* Base for integral numbers.  */
    int base;
    /* Integral holding variables.  */
    long int num;
    unsigned long int unum;
#if FLOATS
    /* Floating-point holding variable.  */
    long double fp_num;
#endif
    /* Character-buffer pointer.  */
    register char *str;
    /* Workspace.  */
    char work[256];
    char *w;		/* Pointer into WORK.  */
    
    if ((format == NULL) || (!*format))
    {
	errno = EINVAL;
	input_error();
    }
    
# define decimal ('.')	/* should really come from locale stuff that we dont */
    /* have as yet */
    c = inchar();
    
    /* Run through the format string.  */
    while (*f != '\0')
    {
#if 0 /* no mb support as yet */
	if (!isascii(*f))
	{
	    /* Non-ASCII, may be a multibyte.  */
	    int len = mblen(f, strlen(f));
	    if (len > 0)
	    {
		while (len-- > 0)
		    if (c == EOF)
			input_error();
		    else if (c == *f++)
			(void) inchar();
		    else
			conv_error();
		continue;
	    }
        }
#endif      
	
	fc = *f++;
	if (fc != '%')
	{
	    /* Characters other than format specs must just match.  */
	    if (c == EOF)
		input_error();
	    if (isspace(fc))
	    {
		/* Whitespace characters match any amount of whitespace.  */
		while (isspace (c))
		    (void)inchar ();
		continue;
	    }
	    else if (c == fc)
		(void) inchar();
	    else
		conv_error();
	    continue;
	}
	
	/* Check for the assignment-suppressant.  */
	if (*f == '*')
	{
	    do_assign = 0;
	    ++f;
	}
	else
	    do_assign = 1;
	
	/* Find the maximum field width.  */
	width = 0;
	while (isdigit(*f))
	{
	    width *= 10;
	    width += *f++ - '0';
	}
	if (width == 0)
	    width = -1;
	
	/* Check for type modifiers.  */
	is_short = is_long = is_long_double = 0;
	while (*f == 'h' || *f == 'l' || *f == 'L')
	    switch (*f++)
	    {
	      case 'h':
		/* int's are short int's.  */
		is_short = 1;
		break;
	      case 'l':
#ifdef HAVE_LONGLONG
		if (is_long)
		    /* A double `l' is equivalent to an `L'.  */
		    is_longlong = 1;
		else
#endif
		    /* int's are long int's.  */
		    is_long = 1;
		break;
	      case 'L':
		/* double's are long double's, and int's are long long int's.  */
		is_long_double = 1;
		break;
	    }
	
	/* End of the format string?  */
	if (*f == '\0')
	    conv_error();
	
	/* Find the conversion specifier.  */
	w = work;
	fc = *f++;
	if (fc != '[' && fc != 'c' && fc != 'n')
	    /* Eat whitespace.  */
	    while (isspace(c))
		(void) inchar();
	switch (fc)
	{
	  case '%':	/* Must match a literal '%'.  */
	    if (c != fc)
		conv_error();
	    else
		c = inchar();
	    break;
	    
	  case 'n':	/* Answer number of assignments done.  */
	    if (do_assign)
		*va_arg(arg, int *) = read_in - 1; /* -1 is debatable ++jrb */
	    break;
	    
	  case 'c':	/* Match characters.  */
	    if (do_assign)
	    {
		str = va_arg(arg, char *);
		if (str == NULL)
		    conv_error();
	    }
	    
	    if (c == EOF)
		input_error();
	    
	    if (width == -1)
		width = 1;
/* mjr:	*/	    
	    if (do_assign) {
		do
		    *str++ = c;
		while (inchar() != EOF && --width > 0);
	    } else
		while (inchar() != EOF && --width > 0)
		    ;

	    if (do_assign)
		++done;
	    
	    if (c == EOF)
		input_error();
	    break;
	    
	  case 's':	/* Read a string.  */
	    if (do_assign)
	    {
		str = va_arg(arg, char *);
		if (str == NULL)
		    conv_error();
	    }
	    
	    if (c == EOF)
		input_error();
	    
	    do
	    {
		if (isspace(c))
		    break;
		if (do_assign)
		    *str++ = c;
		if (width > 0 && --width == 0)
		    break;
	    } while (inchar() != EOF);
	    
	    if (do_assign)
	    {
		*str = '\0';
		++done;
	    }
	    
	    if (c == EOF)
		input_error();
	    break;
	    
	  case 'x':	/* Hexadecimal integer.  */
	  case 'X':	/* Ditto.  */ 
	    base = 16;
	    goto number;
	    
	  case 'o':	/* Octal integer.  */
	    base = 8;
	    goto number;
	    
	  case 'u':	/* Decimal integer.  */
	  case 'd':	/* Ditto.  */
	    base = 10;
	    goto number;
	    
	  case 'i':	/* Generic number.  */
	    base = 0;
	    
	  number:;
	    if (c == EOF)
		input_error();
	    
	    /* Check for a sign.  */
	    if (c == '-' || c == '+')
	    {
		*w++ = c;
		if (width > 0)
		    --width;
		(void) inchar();
	    }
	    
	    /* Look for a leading indication of base.  */
	    if (c == '0')
	    {
		if (width > 0)
		    --width;
		*w++ = '0';
		
		(void) inchar();
		
		if (tolower(c) == 'x')
		{
		    /* one char look ahead to see if its really a lead ind */
		    int savec = c;
		    int peekc = inchar();
		    c = savec;
		    (void)unchar(peekc);
		    if(isxdigit(peekc))
                    {  
			if (base == 0)
			    base = 16;
			if (base == 16)
			{
			    if (width > 0)
				--width;
			    (void) inchar();
			}
		    }
		}
		else if (base == 0)
		    if((c >= '0') && (c <= '7')) 
			base = 8;
	    }
	    
	    if (base == 0)
		base = 10;
	    
	    /* Read the number into WORK.  */
	    do
	    {
		if (base == 16 ? !isxdigit(c) :
		    (!isdigit(c) || ((c - '0') >= base)))
		    break;
		*w++ = c;
		if (width > 0)
		    --width;
	    } while (inchar() != EOF && width != 0);
	    
	    if (w == work ||
		(w - work == 1 && (work[0] == '+' || work[0] == '-')))
		/* There was no number.  */
		conv_error();
	    
	    /* Convert the number.  */
	    *w = '\0';
	    num = strtol(work, &w, base);
	    if (w == work)
		conv_error();
	    
	    if (do_assign)
	    {
		if (is_longlong)
		    *va_arg(arg, LONGLONG int *) = num;
		else if (is_long)
		    *va_arg(arg, long int *) = num;
		else if (is_short)
		    *va_arg(arg, short int *) = (short int) num;
		else
		    *va_arg(arg, int *) = (int) num;
		++done;
	    }
	    break;
	    
#if FLOATS
	  case 'e':	/* Floating-point numbers.  */
	  case 'E':
	  case 'f':
	  case 'g':
	  case 'G':
	    if (c == EOF)
		input_error();
	    
	    /* Check for a sign.  */
	    if (c == '-' || c == '+')
	    {
		*w++ = c;
		if (inchar() == EOF)
		    input_error();
		if (width > 0)
		    --width;
	    }
	    
	    got_dot = got_e = 0;
	    do
	    {
		if (isdigit(c))
		    *w++ = c;
		else if (got_e && w[-1] == 'e' && (c == '-' || c == '+'))
		    *w++ = c;
		else if (!got_e && tolower(c) == 'e')
		{
		    *w++ = 'e';
		    got_e = got_dot = 1;
		}
		else if (c == decimal && !got_dot)
		{
		    *w++ = c;
		    got_dot = 1;
		}
		else
		    break;
		if (width > 0)
		    --width;
	    } while (inchar() != EOF && width != 0);
	    
	    if (w == work)
		conv_error();
#if 0
	    if (w[-1] == '-' || w[-1] == '+' || w[-1] == 'e')
		conv_error();
#else
	    if (w[-1] == '-' || w[-1] == '+')
		conv_error();

	    if(tolower(w[-1]) == 'e')
	    {
		unchar(c);
	        --w;
	        c = *w;
	    }
#endif	    
	    /* Convert the number.  */
	    *w = '\0';
	    fp_num = strtod(work, &w);
	    if (w == work)
		conv_error();
	    
	    if (do_assign)
	    {
		if (is_long_double)
		    *va_arg(arg, long double *) = fp_num;
		else if (is_long)
		    *va_arg(arg, double *) = (double) fp_num;
		else
		    *va_arg(arg, float *) = (float) fp_num;
		++done;
	    }
	    break;
#endif /* FLOATS */
	    
	  case '[':	/* Character class.  */
	    if (do_assign)
	    {
		str = va_arg(arg, char *);
		if (str == NULL)
		    conv_error();
	    }
	    
	    if (c == EOF)
		input_error();
	    
	    if (*f == '^')
	    {
		++f;
		not_in = 1;
	    }
	    else
		not_in = 0;
	    w = (char *)f;		/* remember start of class */
	    bzero (work, 256);
	    while ((fc = *f++) != '\0' && (fc != ']' || f - 1 == w))
	    {
		/* Add the character to the list.  */
		work[(unsigned char)fc] = 1;
		/* Look ahead for a range.  */
		if (f[0] == '-' && f[1] != '\0' && f[1] != ']')
		  {
		    /* Add all characters from the one before the '-'
		       up to the next format char.  */
		    unsigned char end = f[1];
		    while ((unsigned char)++fc <= end)
			work[(unsigned char)fc] = 1;
		    f += 2;
		  }
	    }
	    if (fc == '\0')
		conv_error();
	    
	    work[0] = not_in;
	    unum = read_in;
	    do
	    {
		if (work[(unsigned char)c] == not_in)
		    break;
		if (do_assign)
		    *str++ = c;
		if (width > 0)
		    --width;
	    } while (inchar() != EOF && width != 0);
	    if (read_in == unum)
		conv_error();
	    
	    if (do_assign)
	    {
		*str = '\0';
		++done;
	    }
	    break;
	    
	  case 'p':	/* Generic pointer.  */
	    base = 16;
	    /* A PTR must be the same size as a `long int'.  */
	    is_long = 1;
	    goto number;
	}
    }
    
    conv_error();
}
