/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
*/

/*	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.	*/

#ident	"$Header: doscan.c 1.11 90/08/08 $@(#)libc-port:stdio/doscan.c	2.18"
/*LINTLIBRARY*/
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <varargs.h>
#include <values.h>
#include <errno.h>

#ifdef NL_ARGMAX
#undef NL_ARGMAX
#endif
#define NL_ARGMAX	20

#define OLD	0
#define NEW	1

#define NONE_ASG	0
#define INT_INT		1
#define SHORT_SHORT	2
#define LONG_LONG	3
#define DBL_DBL		4
#define FLOAT_FLOAT	5
#define CHAR_S		6
#define UNSIGNED	7

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


#define FALSE 0
#define TRUE  1

extern double atof();
static int chcount,flag_eof;

struct arg_info {
	int arg_type;	/* int, char, double, etc	*/
	int arg_stat;	/* old or new			*/
	int arg_asgn;	/* assigned value or is '*'	*/
	int arg_exist;	/* matching argument found	*/
	union {
		int	intval;
		short	shortval;
		long	longval;
		double	dblval;
		float	floatval;
		char	charval;
		unsigned	unsignval;
	} v;
} arg_data[NL_ARGMAX];

extern char _numeric[];
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,
		sizel = 0,
		sizeh = 0,
		fcode,
		rtn_val,
		*top_stack;
	
	int var_found = FALSE;		/* global new fmt flag		*/
	int var_format = FALSE;		/* single field format flag	*/
	int pos_found;
	int end_found;			/* end of format arg found	*/
	int length;			/* length modifier		*/
	char *format_start,
		*pos, *pos1, *pos2;
	int high_arg;			/* highest argument number	*/
	int i, num;
	int	val_asgn,		/* is a '*' field?		*/
		lflag, hflag, pflag;
	int arg_pos, arg_val;
	int done;
	int error = FALSE;

	chcount = 0; flag_eof = 0;
	top_stack = (int *)va_alist;

	/* X/Open extension -- process arguments out of order.  We
	 * preprocess the format string to determine whether any are of
	 * the NEW type.
	 */
	
	for (i = 0; i < NL_ARGMAX; ++i) {
		arg_data[i].arg_type = NONE_ASG;
		arg_data[i].arg_stat = OLD;
		arg_data[i].arg_asgn = TRUE;
		arg_data[i].arg_exist = FALSE;
	}

	high_arg = 0;
	pos = format_start = (char *)fmt;

	while (NULL != (pos1 = strchr(pos, '%'))) {
		if ('%' == (fcode = *(++pos1)))
			++pos1;
		else {
			var_format = FALSE;
			val_asgn = TRUE;
			hflag = 0;
			lflag = 0;
			pflag = 0;
			num = 0;	/* arg number	*/
			while (isdigit(fcode)) {
				num = 10*num + (fcode - '0');
				fcode = *(++pos1);
			}
			/* X/Open variable arg format	*/
			if ('$' == fcode) {
				++pos1;
				var_format = TRUE;
				var_found = TRUE;
			} else {
				/* fixed arg format	*/
				--pos1;
			}
			end_found = FALSE;

			/* pass flags/options/precision/width params	*/
			while (! end_found) {
				fcode = *pos1++;
				switch (fcode) {
				case '-':	case '+':	case '#':
				case ' ':	case '0':	case '1':
				case '2':	case '3':	case '4':
				case '5':	case '6':	case '7':
				case '8':	case '9':
					break;
				case 'l':
					lflag = TRUE;
					break;
				case 'h':
					hflag = TRUE;
					break;
				case '*':
					val_asgn = FALSE;
					break;
				case '[':
					while (']' != (fcode = *pos1++) && 
						fcode != '\0')
;						;
					--pos1;
					end_found = TRUE;
					break;
				default:
					end_found = TRUE;
					break;
				}
			}
			switch (fcode) {
			case 'p':
				pflag = TRUE;
				goto dohex;
			case 'd':
			case 'i':
			case 'u':
			case 'o':
			case 'x':
			case 'X':
			case 'n':
				pflag = FALSE;
dohex:
				if (var_found) {
				  if (lflag)
				    arg_data[num-1].arg_type = LONG_LONG;
			  	  else if (hflag)
				    arg_data[num-1].arg_type = SHORT_SHORT;
				  else if (pflag)
				    arg_data[num-1].arg_type = UNSIGNED;
				  else
				    arg_data[num-1].arg_type = INT_INT;

				  arg_data[num-1].arg_stat = NEW;
				  if (num > high_arg)
					high_arg = num;
			  	  arg_data[num-1].arg_asgn = val_asgn;
				}
				break;
			case 'f':
			case 'e':
			case 'E':
			case 'g':
			case 'G':
				if (var_found) {
					if (lflag)
						arg_data[num-1].arg_type = DBL_DBL;
					else
						arg_data[num-1].arg_type = FLOAT_FLOAT;
					arg_data[num-1].arg_stat = NEW;
					if (num > high_arg)
						high_arg = num;
					arg_data[num-1].arg_asgn = val_asgn;
				}
				break;
			case 'c':
			case 's':
			case ']':
				if (var_found) {
					arg_data[num-1].arg_type = CHAR_S;
					arg_data[num-1].arg_stat = NEW;
					if (num > high_arg)
						high_arg = num;
					arg_data[num-1].arg_asgn = val_asgn;
				}
				break;
			default:
				break;

			}
		}
		pos = pos1;
	}

	/* if new X/Open format has been found, perform err check to
	 * ensure that
	 *	1 all args given appear in format string
	 *	2 all arg formats must be of same type
	 *
	 * if any errs found, we treat fmt string as fixed format
	 */
	
	if (var_found) {
		/* each arg must be appear in format string	*/
		for (i = 0; i < high_arg; ++i) {
			if (NONE_ASG == arg_data[i].arg_type)
				error = TRUE;
			if (arg_data[i].arg_stat != NEW)
				error = TRUE;
		}
		if (error)
			var_found = FALSE;
	}


	/*******************************************************
	 * Main loop: reads format to determine a pattern,
	 *		and then goes to read input stream
	 *		in attempt to match the pattern.
	 *******************************************************/
	fmt = (unsigned char *) format_start;
	for (;;) {
		if ('\0' == (ch = *fmt++)) {
			rtn_val = nmatch;
			goto finish;
		}
		if (isspace(ch)) 
		{
		  	if (!flag_eof) 
			{
			   while (isspace(inchar = locgetc()))
;	 		        ;
			   if (locungetc(inchar) == EOF)
				flag_eof = 1;
		        }
		  continue;
                }
		if (ch != '%' || (ch = *fmt++) == '%') 
                {
			if ( (inchar = locgetc()) == ch )
				continue;
			if (locungetc(inchar) != EOF) {
				rtn_val = nmatch;
				goto finish;
			}
			break;
		}
		if (var_found) {
			num = 0;
			while (isdigit(ch)) {
				num = 10*num + (ch - '0');
				ch = *fmt++;

			}
			if ('$' == ch)
				ch = *fmt++;
			else
				exit(2);
		}
		if (ch == '*') 
		{
			stow = 0;
			ch = *fmt++;
		}
		else
			stow = 1;

		/* 
		 * field width into "len":
		 */
		for (len = 0; isdigit(ch); ch = *fmt++)
			len = len * 10 + ch - '0';
		if (len == 0)
			len = MAXINT;
		if ('l' == (size = ch)) {
			ch = *fmt++;
			sizel = TRUE;
		} else
			sizel = FALSE;
		if ('h' == size) {
			ch = *fmt++;
			sizeh = TRUE;
		} else
			sizeh = FALSE;
		if (ch == '\0' ||
			'[' == ch && NULL == (fmt = setup(fmt, tab))) {
			ch = _tolower(ch);
			if (sizeh) {
				size = 'h';
				sizeh = FALSE;
			} else if (sizel) {
				size = 'l';
				sizel = FALSE;
			} else
				size = ch;
		}
		/*
		 * X, E, and G are the same as x, e, and g (XPG3).
		 * Others we can do the same as the traditional
		 * implementation.  Undocumented.
		 */
		if (isupper(ch)) {
			if ('X' != ch && 'G' != ch && 'E' != ch) {
				size = 'l';
			}
			ch = _tolower(ch);
		}
		if (ch!= 'n' && !flag_eof)
		  if (ch != 'c' && ch != '[') 
		  {
			while (isspace(inchar = locgetc()))
				;
			if(locungetc(inchar) == EOF )
				break;
		  }
		switch(ch) {
		case 'c':
		case 's':
		case '[':
			if (var_found) {
				size = string1(stow,ch,len,tab,iop,top_stack,num - 1);
			} else
				size = string(stow,ch,len,tab,iop,&va_alist);
			break;
                case 'n':
			if ('h' == size) {
				if (var_found) {
					arg_data[num-1].v.shortval = (short) chcount;
					arg_data[num-1].arg_exist = TRUE;
				} else if (stow)
					*va_arg(va_alist, short *) = (short) chcount;
			} else if ('l' == size) {
				if (var_found) {
					arg_data[num-1].v.longval = (long) chcount;
					arg_data[num-1].arg_exist = TRUE;
				} else if (stow)
					*va_arg(va_alist, long *) = (long) chcount;
			} else {
				if (var_found) {
					arg_data[num-1].v.intval = (int) chcount;
					arg_data[num-1].arg_exist = TRUE;
				} else if (stow)
					*va_arg(va_alist, int *) = (int) chcount;
			}
			continue;
		 case 'i':
                 default:
			 size = number(stow, ch, len, size, iop, &va_alist, num-1, var_found, pflag);
			 break;
                 }
		 if (size) 
			 nmatch += stow;   
                 else {
			if (flag_eof && !nmatch)
				rtn_val = EOF;
			else
				rtn_val = nmatch;
			goto finish;
		 }
                 continue;
	}
	if (nmatch != 0)
		rtn_val = nmatch;
	else
		rtn_val = EOF;
finish:

	/* if variable format was found, return values to stack	*/
	if (var_found) {
		for (i = 0; i < high_arg; ++i) {
			switch(arg_data[i].arg_type) {
			case INT_INT:
				if (arg_data[i].arg_asgn && arg_data[i].arg_exist)
					*va_arg(va_alist, int *) = arg_data[i].v.intval;
				else
					(void) va_arg(va_alist, int *);
				break;
			case SHORT_SHORT:
				if (arg_data[i].arg_asgn && arg_data[i].arg_exist)
					*va_arg(va_alist, short *) = arg_data[i].v.shortval;
				else
					(void) va_arg(va_alist, short *);
				break;
			case LONG_LONG:
				if (arg_data[i].arg_asgn && arg_data[i].arg_exist)
					*va_arg(va_alist, long *) = arg_data[i].v.longval;
				else
					(void) va_arg(va_alist, long *);
				break;
			case DBL_DBL:
				if (arg_data[i].arg_asgn && arg_data[i].arg_exist)
					*va_arg(va_alist, double *) = arg_data[i].v.dblval;
				else
					(void) va_arg(va_alist, double *);
				break;
			case FLOAT_FLOAT:
				if (arg_data[i].arg_asgn && arg_data[i].arg_exist)
					*va_arg(va_alist, float *) = arg_data[i].v.floatval;
				else
					(void) va_arg(va_alist, float *);
				break;
			case CHAR_S:
					*va_arg(va_alist, char *);
				break;
			case UNSIGNED:
				*va_arg(va_alist, unsigned *) = arg_data[i].v.unsignval;
				break;
			default:
				break;
			}
		}
	}
	return rtn_val;
}

/***************************************************************
 * 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, num, var_flag, pflag)
int stow, type, len, size, num, var_flag, pflag;
register FILE *iop;
va_list *listp;
{
	char numbuf[64], inchar, lookahead;
	register char *np = numbuf;
	register int c, base;
	int digitseen = 0, dotseen = 0, expseen = 0, floater = 0, negflg = 0;
	long lcval = 0;
	int pflag = 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;
	case 'p':
		pflag = 1;
		base = 16;
		break;
	default:
		return(0); /* unrecognized conversion character */
	}
	switch(c = locgetc()) 
	{
	case '-':
		negflg++;
	case '+': /* fall-through */
		if (--len <= 0)
		   break;
		if ( (c = locgetc()) != '0')
		   break;
        case '0':
                if ( (type != 'i') || (len <= 1) )  
		   break;
	        if ('x' == (inchar = locgetc()) || 'X' == inchar)
	        {
		      /* If not using sscanf and *
		       * at the buffer's end     *
		       * then LOOK ahead         */

                   if ( (iop->_file == -1) || (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;
                }
	}
	for (; --len  >= 0 ; *np++ = c, c = locgetc()) 
	{
		if (np > numbuf + 62)           
		{
		    errno = ERANGE;
		    return(0);
                }
		if (isdigit(c) || base == 16 && isxdigit(c)) 
		{
			int digit = c - (isdigit(c) ? '0' :  
			    isupper(c) ? 'A' - 10 : 'a' - 10);
			if (digit >= base)
				break;
			if (stow && !floater)
				lcval = base * lcval + digit;
			digitseen++;

			continue;
		}
		if (!floater)
			break;
		if ((c == _numeric[0] || c == '.') && !dotseen++)
			continue;
		if ( (c == 'e' || c == 'E') && digitseen && !expseen++) 
                {
			*np++ = c;
			c = locgetc();
			--len;
			if (isdigit(c) || c == '+' || c == '-' || isspace(c))
				continue;
		}
		break;
	}


	if (digitseen)
		if(floater) 
		{
#ifndef i386
			register double dval;
#else
			double dval;
#endif
	
			*np = '\0';
			dval = atof(numbuf);
			if (negflg)
				dval = -dval;
			if ('l' == size) {
				if (var_flag) {
					arg_data[num].v.dblval = dval;
					arg_data[num].arg_exist = TRUE;
				} else if (stow)
					*va_arg(*listp, double *) = dval;
			} else {
				if (var_flag) {
					arg_data[num].v.floatval = (float) dval;
					arg_data[num].arg_exist = TRUE;
				} else if (stow) {
					float *pf;
					pf = va_arg(*listp, float *);
					*pf = (float) dval;
				}
			}
		}
		else
		{
			/* suppress possible overflow on 2's-comp negation */
			if (negflg && lcval != HIBITL)
				lcval = -lcval;
			if ('l' == size) {
				if (var_flag) {
					arg_data[num].v.longval = lcval;
					arg_data[num].arg_exist = TRUE;
				} else if (stow)
					*va_arg(*listp, long *) = lcval;
			} else if ('h' == size) {
				if (var_flag) {
					arg_data[num].v.shortval = (short) lcval;
					arg_data[num].arg_exist = TRUE;
				} else if (stow)
					*va_arg(*listp, short *) = (short) lcval;
			} else {
				if (var_flag) {
					if (pflag) {
						arg_data[num].v.unsignval = (unsigned int) lcval;
						arg_data[num].arg_exist = TRUE;
					} else {
						arg_data[num].v.intval = (int) lcval;
						arg_data[num].arg_exist = TRUE;
					}
				} else if (stow) {
					if (pflag)
						*va_arg(*listp, unsigned int *) = (unsigned int) lcval;
					else
						*va_arg(*listp, int *) = (int) lcval;
				}
			}
		}
	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;

	if (stow)
		ptr = va_arg(*listp, char *);
	else
		ptr = NULL;
	start = ptr;

	if (type == 'c' && len == MAXINT)
		len = 1;
	
	while ((ch = locgetc()) != EOF &&
	    !((type == 's' && isspace(ch)) || (type == '[' && tab[ch])))
        {
		if (stow) 
			*ptr = ch;
		ptr++;
		if (--len <= 0)
			break;
	}
	if (ch == EOF ) 
	{
	       flag_eof = 1;
	       chcount-=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 */
}

/*
 * string1 -- this routine processes string arguments out of order.  Since we
 * 	are handling strings, we need the location of the character pointer
 *	passed to us to use in forming the requested string.  Since we are
 *	handling only variable format, we set the stack pointer to the top
 *	and pop off the appropriate number of entries.  When we have the
 *	correct pointer, we reset the stack to the top.  Final assignments
 *	of values other than strings are not done until after the normal
 *	processing is completed just prior to the return (see the label
 *	"finish:")
 */
static int
string1(stow, type, len, tab, iop, va_alist, num)
register int stow, type, len;
register char *tab;
register FILE *iop;
va_dcl
/* va_list *listp; */
{
	va_list ap;
	register int ch;
	register char *ptr;
	char *start;
	int i;
	va_start(ap);
	for (i = 0; i < num; ++i) {
		switch (arg_data[i].arg_type) {
		case INT_INT:
			(void) va_arg(va_alist, int *);
			break;
		case SHORT_SHORT:
			(void) va_arg(va_alist, short *);
			break;
		case LONG_LONG:
			(void) va_arg(va_alist, long *);
			break;
		case DBL_DBL:
			(void) va_arg(va_alist, double *);
			break;
		case FLOAT_FLOAT:
			(void) va_arg(va_alist, float *);
			break;
		case CHAR_S:
			(void) va_arg(va_alist, char *);
			break;
		default:
			break;
		}
	}
	ptr = va_arg(va_alist, char *);
	/* reset pointer list to the top */
	va_start(ap);

	if (! stow)
		ptr = NULL;

	start = ptr;

	if (type == 'c' && len == MAXINT)
		len = 1;
	
	while ((ch = locgetc()) != EOF &&
	    !((type == 's' && isspace(ch)) || (type == '[' && tab[ch])))
        {
		if (stow) 
			*ptr = ch;
		ptr++;
		if (--len <= 0)
			break;
	}
	if (ch == EOF ) 
	{
	       flag_eof = 1;
	       chcount-=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);
}
