#include <stdio.h>
#include <ctype.h>
#include <string.h>
    
/*
 * %efg were loosing big time
 *	fixed  ++jrb
 * all floating conversion now done by atof. much is gained by this.
 *	++jrb
 */
    
#ifndef __NO_FLOAT__
#define FLOATS 1
#endif
    
#ifndef TRUE
#define TRUE  1
#define FALSE 0
#endif
    
extern        char    _numstr[];

#define	skip()	while(isspace(c)) { charcnt++; if ((c=(*get)(ip))<1) goto done; }
#define TEN_MUL(X)	((((X) << 2) + (X)) << 1)

#if FLOATS
/* fp scan actions */
#define F_NADA	0	/* just change state */
#define F_SIGN	1	/* set sign */
#define F_ESIGN	2	/* set exponent's sign */
#define F_INT	3	/* adjust integer part */
#define F_FRAC	4	/* adjust fraction part */
#define F_EXP	5	/* adjust exponent part */
#define F_QUIT	6

#define NSTATE	8
#define FS_INIT		0	/* initial state */
#define FS_SIGNED	1	/* saw sign */
#define FS_DIGS		2	/* saw digits, no . */
#define FS_DOT		3	/* saw ., no digits */
#define FS_DD		4	/* saw digits and . */
#define FS_E		5	/* saw 'e' */
#define FS_ESIGN	6	/* saw exp's sign */
#define FS_EDIGS	7	/* saw exp's digits */

#define FC_DIG		0
#define FC_DOT		1
#define FC_E		2
#define FC_SIGN		3

/* given transition,state do what action? */
int fp_do[][NSTATE] = {
{F_INT,F_INT,F_INT,
     F_FRAC,F_FRAC,
     F_EXP,F_EXP,F_EXP},	/* see digit */
{F_NADA,F_NADA,F_NADA,
     F_QUIT,F_QUIT,F_QUIT,F_QUIT,F_QUIT},	/* see '.' */
{F_QUIT,F_QUIT,
     F_NADA,F_QUIT,F_NADA,
     F_QUIT,F_QUIT,F_QUIT},	/* see e/E */
{F_SIGN,F_QUIT,F_QUIT,F_QUIT,F_QUIT,
     F_ESIGN,F_QUIT,F_QUIT},	/* see sign */
};
/* given transition,state what is new state? */
int fp_ns[][NSTATE] = {
{FS_DIGS,FS_DIGS,FS_DIGS,
     FS_DD,FS_DD,
     FS_EDIGS,FS_EDIGS,FS_EDIGS},	/* see digit */
{FS_DOT,FS_DOT,FS_DD,
 },	/* see '.' */
{0,0,
     FS_E,0,FS_E,
 },	/* see e/E */
{FS_SIGNED,0,0,0,0,
     FS_ESIGN,0,0},	/* see sign */
};
/* which states are valid terminators? */
int fp_sval[NSTATE] = {
    0,0,1,0,1,0,0,1
};
#endif

int
_scanf(ip, get, unget, fmt, args)
register unsigned char *ip;
int (*get) __PROTO((unsigned char *));
int (*unget) __PROTO((int, unsigned char *));
register unsigned char *fmt;
char **args;

{
    register long n;
    register int c, width, lval, sval, cnt = 0, charcnt = 1;
    int store, neg, base, wide1, endnull, rngflag, c2;
    register unsigned char *p;
    unsigned char delim[128], digits[17], *q;
#if FLOATS
    double fx;
    char fbuf[128], *fbp;
    int fstate, trans;
    extern double atof __PROTO((const char *));
#endif
    
    if (!*fmt)
	return(0);
    
    c = (*get)(ip);
    while(c > 0)
    {
	store = FALSE;
	if (*fmt == '%')
	{
	    n	= 0;
	    width	= -1;
	    wide1	= 1;
	    base	= 10;
	    lval	= FALSE;
	    sval	= FALSE;
	    store	= TRUE;
	    endnull	= TRUE;
	    neg	= -1;
	    
	    strcpy((char *)delim,  "\011\012\013\014\015 ");
	    strcpy((char *)digits, (const char *)_numstr); /* "01234567890ABCDEF" */
	    
	    if (fmt[1] == '*')
	    {
		endnull = store = FALSE;
		++fmt;
	    }
	    
	    while (isdigit(*++fmt))		/* width digit(s) */
	    {
		if (width == -1)
		    width = 0;
		wide1 = width = TEN_MUL(width) + (*fmt - '0');
	    }
	    --fmt;
	  fmtnxt:
	    ++fmt;
	    switch(tolower(*fmt))	/* tolower() is a MACRO! */
	    {
	      case '*':
		endnull = store = FALSE;
		goto fmtnxt;
		
	      case 'l':	/* long data */
		lval = TRUE;
	      case 'h':	/* short data (for compatibility) */
		sval = TRUE;
		goto fmtnxt;
		
	      case 'i':	/* any-base numeric */
		base = 0;
		goto numfmt;
		
	      case 'b':	/* unsigned binary */
		base = 2;
		goto numfmt;
		
	      case 'o':	/* unsigned octal */
		base = 8;
		goto numfmt;
		
	      case 'x':	/* unsigned hexadecimal */
		base = 16;
		goto numfmt;
		
	      case 'd':	/* SIGNED decimal */
		neg = FALSE;
		/* FALL-THRU */
		
	      case 'u':	/* unsigned decimal */
	      numfmt:					skip();
		
		if (isupper(*fmt))
		    lval = TRUE;
		
		if (!base)
		{
		    base = 10;
		    neg = FALSE;
		    if (c == '%')
		    {
			base = 2;
			goto skip1;
		    }
		    else if (c == '0')
		    {
			charcnt++;
			c = (*get)(ip);
			if (c < 1)
			    goto savnum;
			if ((c != 'x')
			    && (c != 'X'))
			{
			    base = 8;
			    digits[8]= '\0';
			    goto zeroin;
			}
			base = 16;
			goto skip1;
		    }
		}
		
		if ((neg == FALSE) && (base == 10)
		    && ((neg = (c == '-')) || (c == '+')))
		{
		  skip1:
		    charcnt++;
		    c = (*get)(ip);
		    if (c < 1)
			goto done;
		}
		
		digits[base] = '\0';
		p = ((unsigned char *)
		     strchr((const char *)digits,toupper(c)));
		
		if ((!c || !p) && width)
		    goto done;
		
		while (p && width-- && c)
		{
		    n = (n * base) + (p - digits);
		    charcnt++;
		    c = (*get)(ip);
		  zeroin:
		    p = ((unsigned char *)
			 strchr((const char *)digits,toupper(c)));
		}
	      savnum:
		if (store)
		{
		    p = ((unsigned char *) *args);
		    if (neg == TRUE)
			n = -n;
		    if (lval)
			*((long*) p) = n;
		    else if (sval)
			*((short *) p) = n;
		    else
			*((int *) p) = n;
		    ++cnt;
		}
		break;
		
#if FLOATS
	      case 'e':	/* float */
	      case 'f':
	      case 'g':
		skip();
		
		if (isupper(*fmt))
		    lval = TRUE;
		
		fstate = FS_INIT;
		fbp = fbuf;
		while (c && width--) {
		    if (c >= '0' && c <= '9')
			trans = FC_DIG;
		    else if (c == '.')
			trans = FC_DOT;
		    else if (c == '+' || c == '-')
			trans = FC_SIGN;
		    else if (tolower(c) == 'e')
			trans = FC_E;
		    else
			goto fdone;
		    
		    *fbp++ = c;

		    if (fp_do[trans][fstate] == F_QUIT)
			goto fdone;
		    fstate = fp_ns[trans][fstate];
		    charcnt++;
		    c = (*get)(ip);
		}
		
	      fdone:
		*fbp = '\0';
		if (!fp_sval[fstate])
		    goto done;
		if (store) {
		    fx = (*fbuf == '\0') ? 0.0 : atof(fbuf);
		    p = (unsigned char *) *args;
		    if (lval)
			*((double *) p) = fx;
		    else
			*((float *) p) = (float)fx;
		    ++cnt;
		}
		break;
#endif
		
	      case 'n':
		if (store) {
		  p = (unsigned char *) *args;
		  *((int *) p) = charcnt;
		}
		break;
		
	      case 'c':	/* character data */
		width = wide1;
		endnull	= FALSE;
		delim[0] = '\0';
		goto strproc;
		
	      case '[':	/* string w/ delimiter set */
		
		/* get delimiters */
		p = delim;
		
		if (*++fmt == '^')
		    fmt++;
		else
		    lval = TRUE;
		
		rngflag = 2;
		if ((*fmt == ']') || (*fmt == '-'))
		{
		    *p++ = *fmt++;
		    rngflag = FALSE;
		}
		
		while (*fmt != ']')
		{
		    if (*fmt == '\0')
			goto done;
		    switch (rngflag)
		    {
		      case TRUE:
			c2 = *(p-2);
			if (c2 <= *fmt)
			{
			    p -= 2;
			    while (c2 < *fmt)
				*p++ = c2++;
			    rngflag = 2;
			    break;
			}
			/* fall thru intentional */
			
		      case FALSE:
			rngflag = (*fmt == '-');
			break;
			
		      case 2:
			rngflag = FALSE;
		    }
		    
		    *p++ = *fmt++;
		}
		
		*p = '\0';
		goto strproc;
		
	      case 's':	/* string data */
		skip();
	      strproc:
		/* process string */
		p = ((unsigned char *) *args);
		
		/* if the 1st char fails, match fails */
		if (width)
		{
		    q = ((unsigned char *)
			 strchr((const char *)delim, c));
		    if((c < 1)
		       || (lval ? !q : (int) q))
		    {
			if (endnull)
			    *p = '\0';
			goto done;
		    }
		}
		
		for (;;) /* FOREVER */
		{
		    if (store)
			*p++ = c;
		    charcnt++;
		    if (((c = (*get)(ip)) < 1) ||
			(--width == 0))
			break;
		    q = ((unsigned char *)
			 strchr((const char *)delim, c));
		    if (lval ? !q : (int) q)
			break;
		}
		
		if (store)
		{
		    if (endnull)
			*p = '\0';
		    ++cnt;
		}
		break;
		
	      case '\0':	/* early EOS */
		--fmt;
		/* FALL THRU */
		
	      default:
		goto cmatch;
	    }
	}
	else if (isspace(*fmt))		/* skip whitespace */
	{
	    skip();
	}
	else 
	{			/* normal match char */
	  cmatch:
	    if (c != *fmt) 
		break;
	    if (fmt[1] == 0) {	/* we're not going to have any more matches */
		return (cnt);
	    }
	    charcnt++;
	    c = (*get)(ip);
	}
	
	if (store)
	    args++;
	
	if (!*++fmt)
	    break;
    }
    
  done:						/* end of scan */
    if ((c < 0) && (cnt == 0))
	return(EOF);
    
    (*unget)(c, ip);
    return(cnt);
}
