#if !defined(lint) && defined(SCCSIDS)
static char sccsid[] = "@(#)doscan.c 1.1 94/10/31 SMI"; /* from S5R3.1 2.18 */
#endif
/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright (c) 1988 by Sun Microsystems, Inc.
 */

/*LINTLIBRARY*/
#include <stdio.h>
#include <ctype.h>
#include <varargs.h>
#include <values.h>
#include <floatingpoint.h>
#include <errno.h>

#define NCHARS	(1 << BITSPERBYTE)
#define locgetc()	(chcount+=1,getc(iop))
#define locungetc(x)	(chcount-=1,ungetc(x,iop))

extern char *memset();
static int chcount,flag_eof;

#ifdef S5EMUL
#define	isws(c)		isspace(c)
#else
/*
 * _sptab[c+1] is 1 iff 'c' is a white space character according to the
 * 4.2BSD "scanf" definition - namely, SP, TAB, and NL are the only
 * whitespace characters.
 */
static char _sptab[1+256] = {
	0,				/* EOF - not a whitespace char */
	0,0,0,0,0,0,0,0,
	0,1,1,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	1,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
};

#define	isws(c)		((_sptab + 1)[c] != 0)
#endif

int
_doscan(iop, fmt, va_alist)
register FILE *iop;
register unsigned char *fmt;
va_list va_alist;
{
	extern unsigned char *setup();
	char tab[NCHARS];
	register int ch;
	int nmatch = 0, len, inchar, stow, size;
	chcount=0; flag_eof=0;

	/*******************************************************
	 * Main loop: reads format to determine a pattern,
	 *		and then goes to read input stream
	 *		in attempt to match the pattern.
	 *******************************************************/
	for ( ; ; )
	{
		if ( (ch = *fmt++) == '\0')
			return(nmatch); /* end of format */
		if (isws(ch))
		{
		  	if (!flag_eof) 
			{
			   while (isws(inchar = locgetc()))
				;
			   if (inchar == EOF) {
				chcount--;
				flag_eof = 1;
			   }
			   else if (locungetc(inchar) == EOF)
				flag_eof = 1;
			}
		  continue;
		}
		if (ch != '%' || (ch = *fmt++) == '%')
                {
			if ( (inchar = locgetc()) == ch )
				continue;
			if (inchar != EOF) {
				if (locungetc(inchar) != EOF)
					return(nmatch); /* failed to match input */
			} else {
				chcount--;
			}
			break;
		}
		if (ch == '*')
		{
			stow = 0;
			ch = *fmt++;
		}
		else
			stow = 1;

		for (len = 0; isdigit(ch); ch = *fmt++)
			len = len * 10 + ch - '0';
		if (len == 0)
			len = MAXINT;
		if ( (size = ch) == 'l' || (size == 'h') || (size == 'L') )
			ch = *fmt++;
		if (ch == '\0' ||
		    ch == '[' && (fmt = setup(fmt, tab)) == NULL)
			return(EOF); /* unexpected end of format */
		if (isupper(ch))  /* no longer documented */
		{
			/*
			 * The rationale behind excluding the size
			 * of 'L' is that the 'L' size specifier was
			 * introduced in ANSI/ISO-C.  If the user
			 * specifies a format of %LG, it can mean
			 * nothing other than "long double", be the
			 * code ANSI or not.  Mapping it to "double"
			 * makes no sense.
			 */
			if (size != 'L')
				size = 'l';
#ifdef S5EMUL
			ch = _tolower(ch);
#else
			ch = tolower(ch);
#endif
		}
		switch(ch)
		{
		 case 'c':
		 case 's':
		 case '[':
			  if ((size = string(stow,ch,len,tab,iop,&va_alist)) < 0)
				goto out;	/* EOF seen, nothing converted */
			  break;
                 case 'n':
			  if (stow == 0)
				continue;
			  if (size == 'h')
				*va_arg(va_alist, short *) = (short) chcount;
		          else if (size == 'l')
				*va_arg(va_alist, long *) = (long) chcount;
			  else
			  	*va_arg(va_alist, int *) = (int) chcount;
			  continue;
                 default:
			 if ((size = number(stow, ch, len, size, iop, &va_alist)) < 0)
				goto out;	/* EOF seen, nothing converted */
			 break;
                 }
		   if (size)
			nmatch += stow;
		   else 
			return((flag_eof && !nmatch) ? EOF : nmatch);
		continue;
	}
out:
	return (nmatch != 0 ? nmatch : EOF); /* end of input */
}

/***************************************************************
 * Functions to read the input stream in an attempt to match incoming
 * data to the current pattern from the main loop of _doscan().
 ***************************************************************/
static int
number(stow, type, len, size, iop, listp)
int stow, type, len, size;
register FILE *iop;
va_list *listp;
{
	char numbuf[64], inchar, lookahead;
	register char *np = numbuf;
	register int c, base;
	int digitseen = 0, floater = 0, negflg = 0;
	long lcval = 0;
	switch(type)
	{
	case 'e':
	case 'f':
	case 'g':
		floater++;
	case 'd':
	case 'u':
	case 'i':
		base = 10;
		break;
	case 'o':
		base = 8;
		break;
	case 'x':
		base = 16;
		break;
	default:
		return(0); /* unrecognized conversion character */
	}
	if (!flag_eof)
	{
		while (isws(c = locgetc()))
			;
	}
	else
		c = locgetc();
	if (c == EOF) {
		chcount--;
		return(-1);	/* EOF before match */
	}
        if (floater != 0) {     /* Handle floating point with
                                 * file_to_decimal. */
                decimal_mode    dm;
                decimal_record  dr;
                fp_exception_field_type efs;
                enum decimal_string_form form;
                char           *echar;
                int             nread, ic;
                char            buffer[1024];
                char           *nb = buffer;

                locungetc(c);
		if (len > 1024)
			len = 1024;
                file_to_decimal(&nb, len, 0, &dr, &form, &echar, iop, &nread);
                if (stow && (form != invalid_form)) {
                        dm.rd = fp_direction;
                        if (size == 'l') {      /* double */
                                decimal_to_double((double *) va_arg(*listp, double *), &dm, &dr, &efs);
                        } else if (size == 'L') {      /* quad */
                                decimal_to_quadruple((quadruple *)va_arg(*listp, double *), &dm, &dr, &efs);
                        } else {/* single */
                                decimal_to_single((float *) va_arg(*listp, float *), &dm, &dr, &efs);
                        }
			if ((efs & (1 << fp_overflow)) != 0) {
				errno = ERANGE;
			}
			if ((efs & (1 << fp_underflow)) != 0) {
				errno = ERANGE;
                        }
                }
		chcount += nread;	/* Count characters read. */
                c = *nb;        /* Get first unused character. */
                ic = c;
                if (c == NULL) {
                        ic = locgetc();
                        c = ic;
                        /*
                         * If null, first unused may have been put back
                         * already.
                         */
                }         
                if (ic == EOF) {
                        chcount--;
                        flag_eof = 1;
                } else if (locungetc(c) == EOF)
                        flag_eof = 1;
                return ((form == invalid_form) ? 0 : 1);        /* successful match if
                                                                 * non-zero */
        }
	switch(c) {
	case '-':
		negflg++;
		if (type == 'u')
			break;
	case '+': /* fall-through */
		if (--len <= 0)
			break;
		if ( (c = locgetc()) != '0')
			break;
        case '0':
                if ( (type != 'i') || (len <= 1) )  
		   break;
	        if ( ((inchar = locgetc()) == 'x') || (inchar == 'X') ) 
	        {
		      /* If not using sscanf and *
		       * at the buffer's end     *
		       * then LOOK ahead         */

                   if ( (iop->_flag & _IOSTRG) || (iop->_cnt != 0) )
		      lookahead = locgetc();
		   else
		   {
		      if ( read(fileno(iop),np,1) == 1)
		         lookahead = *np;
                      else
		         lookahead = EOF;
                      chcount += 1;
                   }    
		   if ( isxdigit(lookahead) )
		   {
		       base =16;

		       if ( len <= 2)
		       {
			  locungetc(lookahead);
			  len -= 1;            /* Take into account the 'x'*/
                       }
		       else 
		       {
		          c = lookahead;
			  len -= 2;           /* Take into account '0x'*/
		       }
                   }
	           else
	           {
	               locungetc(lookahead);
	               locungetc(inchar);
                   }
		}
	        else
	        {
		    locungetc(inchar);
	            base = 8;
                }
	}
	if (!negflg || type != 'u')
	    for (; --len  >= 0 ; *np++ = c, c = locgetc()) 
	    {
		if (np > numbuf + 62)           
		{
		    errno = ERANGE;
		    return(0);
                }
		if (isdigit(c))
		{
			register int digit;
			digit = c - '0';
			if (base == 8)
			{
				if (digit >= 8)
					break;
				if (stow)
					lcval = (lcval<<3) + digit;
			}
			else
			{
				if (stow)
				{
					if (base == 10)
						lcval = (((lcval<<2) + lcval)<<1) + digit;
					else /* base == 16 */
						lcval = (lcval<<4) + digit;
				}
			}
			digitseen++;


			continue;
		}
		else if (base == 16 && isxdigit(c))
		{
			register int digit;
			digit = c - (isupper(c) ? 'A' - 10 : 'a' - 10);
			if (stow)
				lcval = (lcval<<4) + digit;
			digitseen++;
			continue;
		}
		break;
	    }


	if (stow && digitseen)
		{
	 	/* suppress possible overflow on 2's-comp negation */
			if (negflg && lcval != HIBITL)
				lcval = -lcval;
			if (size == 'l')
				*va_arg(*listp, long *) = lcval;
			else if (size == 'h')
				*va_arg(*listp, short *) = (short)lcval;
			else
				*va_arg(*listp, int *) = (int)lcval;
		}
	if (c == EOF) {
		chcount--;
		flag_eof=1;
	} else if (locungetc(c) == EOF)
		flag_eof=1;
	return (digitseen); /* successful match if non-zero */
}

static int
string(stow, type, len, tab, iop, listp)
register int stow, type, len;
register char *tab;
register FILE *iop;
va_list *listp;
{
	register int ch;
	register char *ptr;
	char *start;

	start = ptr = stow ? va_arg(*listp, char *) : NULL;
	if (type == 's')
	{
		if (!flag_eof)
		{
			while (isws(ch = locgetc()))
				;
		}
		else
			ch = locgetc();
		if (ch == EOF)
			return(-1);	/* EOF before match */
		while (ch != EOF && !isws(ch))
		{
			if (stow)
				*ptr = ch;
			ptr++;
			if (--len <= 0)
				break;
			ch = locgetc();
		}
	} else if (type == 'c') {
		if (len == MAXINT)
			len = 1;
		while ( (ch = locgetc()) != EOF)
		{
			if (stow)
				*ptr = ch;
			ptr++;
			if (--len <= 0)
				break;
		}
	} else { /* type == '[' */
		while ( (ch = locgetc()) != EOF && !tab[ch])
		{
			if (stow)
				*ptr = ch;
			ptr++;
			if (--len <= 0)
				break;
		}
	}
	if (ch == EOF )
	{
		chcount-=1;
		flag_eof = 1;
	}
	else if (len > 0 && locungetc(ch) == EOF)
		flag_eof = 1;
	if (ptr == start)
		return(0); /* no match */
	if (stow && type != 'c')
		*ptr = '\0';
	return (1); /* successful match */
}

static unsigned char *
setup(fmt, tab)
register unsigned char *fmt;
register char *tab;
{
	register int b, c, d, t = 0;

	if (*fmt == '^')
	{
		t++;
		fmt++;
	}
	(void) memset(tab, !t, NCHARS);
	if ( (c = *fmt) == ']' || c == '-')  /* first char is special */
	{
		tab[c] = t;
		fmt++;
	}
	while ( (c = *fmt++) != ']')
	{
		if (c == '\0')
			return(NULL); /* unexpected end of format */
		if (c == '-' && (d = *fmt) != ']' && (b = fmt[-2]) < d)
		{
			(void) memset(&tab[b], t, d - b + 1);
			fmt++;
		}
		else
			tab[c] = t;
	}
	return (fmt);
}
