#include <stdio.h>
#include <ctype.h>

extern	char	_numstr[];

/* #define	skip()	do{c=(*get)(ip); if (c<1) goto done;}while(isspace(c))*/

#define	skip()	while(isspace(c)) { if ((c=(*get)(ip))<1) goto done; }

#define char_downcase(foo) (isupper(foo) ? tolower(foo) : foo)
#define char_upcase(foo) (islower(foo) ? toupper(foo) : foo)

_scanf(ip, get, unget, fmt, args)
register unsigned char *ip;
int (*get)();
int (*unget)();
register unsigned char *fmt;
char **args;

/*
 *	  _scanf (ver. 1.1c) written 12-21-87 by John Stanley
 *		for DLibs public-domain C source library.
 *
 *	<fmt> points to a format control string.  <args> pointers to a
 *	list of arguments, each of which is the address of a variable in
 *	which input data may be stored.  The format string is used to
 *	control reading of characters from the <get> function.  As each
 *	character is needed <get> is called in the form "c = (*get)(ip);"
 *	where <c> is the character read (negative for errors) and <ip> is
 *	the auxiliary pointer specified by the <ip> parameter.  If a
 *	character needs to be un-gotten, a call to <unget> of the form
 *	"(*unget)(c, ip);" is made.  The format string is composed of
 *	characters and format specifications.  Any characters in <fmt>,
 *	except whitespace characters, which are not part of a format
 *	specifier are expected to be matched one-to-one by characters in
 *	the input stream.  Scanning terminates if a mismatch occurs or if
 *	any call to <get> results in an error.  Whitespace characters
 *	match 0 or more whitespace characters in the input stream.  The
 *	'%' character introduces a format specifier.  The general form of
 *	a format specifier is:
 *
 *		 %[*][<width>][l|h]{d|u|o|x|b|i|c|s}
 *
 *	The '*' specifies that a field is to be scanned by not stored.
 *	No variable pointer should be provided for non-stored format
 *	specs.  The <width> field specifies that maximum number of
 *	characters to be process to fill the given format type.  Less
 *	than <width> characters will be processed if the field ends
 *	before <width> characters have been processed.  A field ends when
 *	either a whitespace character, or a character which does not fit
 *	the specified format, is read.  The preceding 'l' (or
 *	capitalizing the conversion character) specifies that the
 *	associated variable is a "long" type.  The trailing character
 *	specifies the format type, as follows:
 *
 *		d Signed decimal integer
 *		u Unsigned decimal integer
 *		o Unsigned octal integer
 *		x Unsigned hexadecimal integer
 *		b Unsigned binary integer
 *		i Unsigned decimal/octal/hexadecimal/binary integer
 *		c Character
 *		s String
 *
 *	If a <width> is specified with the 'c' format, exactly <width>
 *	characters (including whitespace) are read from the input stream,
 *	and written to a string.  No '\0' character is added If the
 *	character following the '%' is not recognized, it is expected to
 *	match the input stream as a non-format character, thus "%%" is
 *	used to match a single '%' character.
 *
 *	One additional conversion is the brace-format.  Shown as "%[...]",
 *	the '...' represent a list of characters.  If the first character
 *	in the list is a '^', the field contains any characters -not- in
 *	the list (starting with the 1st character after the '^').  If the
 *	first character of the list is not a '^', then the field will
 *	only contain those characters found in the list.  A right brace 
 *	character (']') can be included as one of the list of characters
 *	by placing it as the first character in the list.  If the '^'
 *	negation character is the first character, the included brace
 *	should be the next character after the '^'.  For maximum
 *	portability, a range should be explicitly given (a good example
 *	would be "%[0123456789]"), but to allow for porting from
 *	systems with smarter scanf functions, this version of scanf
 *	also supports ranges represented using a <first>-<last>
 *	form (eg: "%[0-9]").  To use the first-last form, the
 *	character <first> must be lexically less than or equal to
 *	the character <last>.  If this rule is violated, or if the
 *	hyphen is the first or last character of the list, the
 *	hyphen will be assumed to be just another character in the
 *	list and no range expansion will be done.  The resulting
 *	string containing the characters in (or not in) the list
 *	will be null terminated.  It should be noted that, unlike
 *	most of the other formats, this conversion does allow the
 *	programmer to specify that whitespace characters will be
 *	included in the resulting string.
 *
 */

{
	register long n;
	register int c, width, lval, cnt = 0;
	int store, neg, base, wide1, endnull, rngflag, c2;
	register unsigned char *p;
	unsigned char delim[128], digits[17], *q;
	char *strchr(), *strcpy();

	if (!*fmt)
		return(0);

	c = (*get)(ip);
	while(c > 0)
		{
		store = FALSE;
		if (*fmt == '%')
			{
			n	= 0;
			width	= -1;
			wide1	= 1;
			base	= 10;
			lval	= FALSE;
			store	= TRUE;
			endnull	= TRUE;
			neg	= -1;

			strcpy(delim,  "\011\012\013\014\015 ");
			strcpy(digits, "01234567890ABCDEF");

			if (fmt[1] == '*')
				{
				endnull = store = FALSE;
				++fmt;
				}

			while (isdigit(*++fmt))		/* width digit(s) */
				{
				if (width == -1)
					width = 0;
				wide1 = width = (width * 10) + (*fmt - '0');
				}
			--fmt;
fmtnxt:
			++fmt;
/*			switch(tolower(*fmt))	/* tolower() is a MACRO! */
			switch(char_downcase(*fmt))
				{
				case '*':
					endnull = store = FALSE;
					goto fmtnxt;

				case 'l':	/* long data */
					lval = TRUE;
/* for compatability --> */	case 'h':	/* short data */
					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')
							{
							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:
						c = (*get)(ip);
						if (c < 1)
							goto done;
						}

					digits[base] = '\0';
					p = strchr(digits,char_upcase(c));

					if ((!c || !p) && width)
						goto done;

					while (p && width-- && c)
						{
						n = (n * base) + (p - digits);
						c = (*get)(ip);
zeroin:
						p = strchr(digits,char_upcase(c));
						}
savnum:
					if (store)
						{
						p = *args;
						if (neg == TRUE)
							n = -n;
						if (lval)
							*((long*) p) = n;
						else
							*((int *) p) = n;
						++cnt;
						}
					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 = *args;

					/* if the 1st char fails, match fails */
					if (width)
						{
						q = strchr(delim, c);
						if ((c < 1) || (lval ? !(q != 0) : (q != 0)))
							{
							if (endnull)
								*p = '\0';
							goto done;
							}
						}

					for (;;) /* FOREVER */
						{
						if (store)
							*p++ = c;
						if (((c = (*get)(ip)) < 1) ||
						    (--width == 0))
							break;

						q = strchr(delim, c);
						if (lval ? !(q != 0) : (q != 0))
							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;
			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);
}
