/*
 *				d o s c a n . c
 */

/*)LIBRARY
*/

#ifdef	DOCUMENTATION

title	c_doscan	Formatted Input Processor
index		Formatted input processor

synopsis
	.s.nf
	int
	c_doscan(iop, fmt, args)
	FILE		*iov;	/* File descriptor	*/
	char		*fmt;	/* Format string	*/
	int		*arg[];	/* Arg vector		*/
	.s.f
Description

	c_doscan() does parsing for scanf(), etc.  See the description
	of scanf() for more details.

	c_doscan() returns the number of items matched.

Bugs

	Character classes and float/double are not supported.

#endif

#include	<stdio.h>
#include	<ctype.h>

#define	FALSE	0
#define	TRUE	1
#define	EOS	0
#define	HUGE	32767			/* Infinite width field		*/

typedef	char	* POINTER;

int
c_doscan(fd, format, args)
FILE			*fd;
register char		*format;
POINTER			*args;
{
	register int		c;		/* Current character	*/
	int			nmatch;		/* Nbr. formats matched	*/
	int			len;		/* Field size		*/
	register POINTER	store_ptr;	/* -> result storage	*/
	int			fileended;	/* TRUE at eof		*/
	extern int		convert();
  
	nmatch = 0;
	fileended = FALSE;
	for (;;) {
	    switch (c = *format++) {
	    case EOS:				/* End of format	*/
		return (nmatch);

	    case '%':				/* Format starts	*/
		if ((c = *format++) == '%')	/* But, "%%" is a char	*/
		    goto any_char;
		if (c == '*') {			/* '*' means don't	*/
		    store_ptr = NULL;		/* store a result.	*/
		    c = *format++;
		}
		else {
		    store_ptr = *args++;
		}
		len = 0;			/* Get field lenth	*/
		while (isdigit(c)) {
		    len = len*10 + (c - '0');
		    c = *format++;
		}
		if (len == 0)
		    len = HUGE;			/* Big field		*/
		if (c == 'l' || c == 'L') {	/* Long?		*/
		    c = *format++;
		    if (islower(c))
			c = toupper(c);
		}
		if (c == EOS)			/* Format stopped in	*/
		    return (-2);		/* mid-stream, so die	*/
		if (convert(store_ptr, c, len, fd, &fileended) != FALSE
		 && store_ptr != NULL)
		    nmatch++;			/* Count a conversion	*/
		if (fileended)
		    return ((nmatch) ? nmatch : -1);
		break;
  
	    case ' ':
	    case '\n':
	    case '\t': 
		break;				/* Ignore whitespace	*/
  
	    default:				/* Random text must	*/
any_char:					/* Match input stream	*/
		if (c != (len = getc(fd))) {
			return((len == EOF) ? -1 : nmatch);
		}
		break;
	    }
	}
}


static int
convert(store_ptr, type, len, fd, eofptr)
POINTER		store_ptr;	/* Result pointer (or NULL)		*/
int		type;		/* Conversion type (character)		*/
int		len;		/* Field length				*/
FILE		*fd;		/* Input file				*/
int		*eofptr;	/* Set TRUE on eof			*/
/*
 * Do the actual conversion.
 */
{
	register char		*np;
	register int		c;
	register int		base;		/* -1/0/+1 for 8/10/16	*/
	int			negative;
	long			value;
	int			ndigit;
	int			size;		/* TRUE if long		*/
	extern int		getstring();
  
	/*
	 * first decide whether it's a string or numeric type
	 */
	if (type=='c' || type=='s') {
	    return (getstring(store_ptr, type, len, fd, eofptr));
	}
	/*
 	 * else, process a number
	 */
	value = 0;
	ndigit = 0;
	size = FALSE;
	if (isupper(type)) {
	    size++;
	    type += ('a' - 'A');
	}
	base = 0;
	if (type == 'o')
		--base;
	else if (type == 'x')
		++base;
	negative = FALSE;
	while ((c = getc(fd)), isspace(c))
	    ;
	if (c == '-') {
	    negative++;
	    c =  getc(fd);
	    len--;
	}
	else if (c == '+') {
	    len--;
	    c = getc(fd);
	}
	while (--len >= 0 && (isdigit(c) || (base > 0 && isxdigit(c)))) {
	    switch (base) {
	    case -1:				/* Octal		*/
		value <<= 3;
		break;

	    case 0:				/* Decimal		*/
		value *= 10;
		break;

	    case 1:				/* Hex			*/
		value <<= 4;
		break;
	    }
	    c -= (isdigit(c)) ? '0'
	      :  (islower(c)) ? ('a' - 10)
	      :			('A' - 10);
	    value += c;
	    c = getc(fd);
	    ndigit++;
	}
	if (negative)
	    value = (-value);
	if (c != EOF) {
	    ungetc(c, fd);
	    *eofptr = FALSE;
	}
	else {
	    *eofptr = TRUE;
	}
	if (store_ptr != NULL && ndigit > 0) {
	    if (size)
		*((long *) store_ptr) = value;
	    else
		*((int *) store_ptr) = value;
	    return (TRUE);
	}
	else
	    return (FALSE);
}
  
static int
getstring(store_ptr, type, len, fd, eofptr)
register char	*store_ptr;	/* Result pointer (or NULL)		*/
int		type;		/* Conversion type (character)		*/
int		len;		/* Field length				*/
FILE		*fd;		/* Input file				*/
int		*eofptr;	/* Set TRUE on eof			*/
{
	register int	c;
	char		*store_start;
  
	*eofptr = FALSE;
	store_start = store_ptr;
	if (type == 'c' && len == HUGE)
	    len = 1;
	if (type == 'c')
	    c = getc(fd);
	else {
	    /*
	     * Skip leading whitespace in a string
	     */
	    while ((c = getc(fd)) != EOF && isspace(c))
		;
	}
	while (--len >= 0 && c != EOF) {
	    if (store_ptr != NULL) {
		*store_ptr++ = c;
	    }
	    c = getc(fd);
	}
	if (c != EOF) {
	    ungetc(c, fd);
	    *eofptr = FALSE;
	}
	else {
	    *eofptr = TRUE;
	}
	if (store_ptr != NULL && store_ptr != store_start) {
	    if (type != 'c')
		*store_ptr = EOS;
		return(TRUE);
	}
	return (FALSE);
}
