Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_FS_1_19910112 - kcc-6/lib/stdio/scanf.c
There are 9 other files named scanf.c in the archive. Click here to see a list.
/*
**	SCANF - parses formatted input text
**
**	(c) Copyright Ken Harrenstien 1989
**		for all changes after v.188, 13-Jun-1988
**	(c) Copyright Ian Macky, SRI International 1986
*/
/*
 *	this scanf code conforms with the description of [f,s]scanf
 *	as described in Harbison & Steele's "C: A Reference Manual",
 *	sections 11.5.28 (scanf), 11.5.16 (fscanf) and 11.5.30 (sscanf)
 */

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

#if __STDC__
#define CONST const
#else
#define CONST
#endif

struct scanf_work {
    FILE *stream;		/* input stream (source) */
    int asscnt;			/* number of successful assignments */
    int ccnt;			/* # of chars read from stream so far */
    va_list args;		/* pointer to arg list */
    CONST char *format;		/* pointer to format string */
    int assign;			/* TRUE if assigning results from this parse */
    int width;			/* maximum field width */
    int size;			/* SHORT, LONG or NORMAL */
    int error;			/* flag set by parse routines if bad input */
};

#define CHAR_SET_SIZE	128	/* size of character set */

static int valid_char[CHAR_SET_SIZE];

/* sf.size says what size object you want the return value to be stored
   as.  internally the largest size is used, which is then cast into
   the requested size for storing */

#define SHORT		-1
#define NORMAL		0
#define LONG		1
#define LNGDBL		2

#ifndef TRUE
#define TRUE	1
#define FALSE	0
#endif

/* Internal routines */
static int _vfscanf();
static void duox_scanf(), f_scanf(),
	c_scanf(), s_scanf(), n_scanf();
static void bracket_scanf();
#if __STDC__
int sscanf(const char *s, const char *format, ...)
#else
int
sscanf(s, format /*, ...*/)
char *s;
char *format;
#endif
{
    FILE *f;
    va_list ap;
    int result;

    va_start(ap, format);		/* set up pointer to args */
    f = sopen(s, "r", strlen(s));	/* set up to read from string */
    result = _vfscanf(f, format, ap);
    va_end(ap);
    fclose(f);				/* nuke the string FILE */
    return result;
}

#if __STDC__
int scanf(const char *format, ...)
#else
int
scanf(format /*, ...*/)
char *format;
#endif
{
    va_list ap;
    int ret;

    va_start(ap, format);
    ret = _vfscanf(stdin, format, ap);
    va_end(ap);
    return ret;
}

#if __STDC__
int fscanf(FILE *stream, const char *format, ...)
#else
int
fscanf(stream, format /*, ...*/)
FILE *stream;
char *format;
#endif
{
    va_list ap;
    int ret;

    va_start(ap, format);
    ret = _vfscanf(stream, format, ap);
    va_end(ap);
    return ret;
}
static int
_vfscanf(stream, format, args)
FILE *stream;
CONST char *format;
va_list args;
{
    struct scanf_work sf;
    int c, ch;

    if (!_readable(stream)) return 0;
    sf.stream = stream;		/* put args 'n' stuff into global storage */
    sf.args = args;		/* so subroutines can get at them */
    sf.format = format;
    sf.asscnt = 0;		/* number of successful parse&assigns */

    /*	it's more efficient for a PDP-10 CPU to do pre-increment
     *	char fetch operations, so we're going to make less elegant C
     *	code which will produce more efficient machine code.  so sue me.
     */
    for (c = *sf.format; c; c = *++sf.format) {

	/*	whitespace in the format string means to skip over whitespace
	 *	in the input stream, if any exists.  any amount of format
	 *	whitespace is semantically equivalent, so skip it all.
	 */
	if (isspace(c)) {
	    while (isspace(c = getc(sf.stream))) sf.ccnt++;
	    if (c == EOF) break;
	    ungetc(c, sf.stream);
	    while (isspace(c = *++sf.format)) ;
	    /* Drop thru to handle non-whitespace char in format string */
	}
	if (c != '%') {
	    /*	if it's not whitespace or a conversion op, then it's just joe
	     *	random character, which must match what comes next in the input
	     *	stream
	     */
	    if (c == (ch = getc(sf.stream))) {
		sf.ccnt++;
		continue;		/* OK, continue main loop! */
	    }
	    ungetc(ch, sf.stream);	/* Ugh, didn't match, restore char */
	    break;			/* and break out of main loop */
	}

	/* a percent starts a conversion specification.  if the next char is
	 * '*', then no assignment is done, the object is just parsed and
	 * skipped over.  there may be a maximum field width, expressed as an
	 * unsigned decimal number, which must be positive.  there may be a
	 * size specification, one of 'h' for short or 'l' (lowercase 'L') for
	 * long.  all of those are optional, but if any appear, they must be
	 * in the order above.  the required part is the conversion op, a
	 * single character, one of:
	 *
	 *		d	signed decimal		== strtol(,,10)
	**		i	integer			== strtol(,,0)
	 *		o	unsigned octal		== strtoul(,,8)
	 *		u	unsigned decimal	== strtoul(,,10)
	 *	     x, X	unsigned hexidecimal	== strtoul(,,16)
	 *  e, E, f, g, G	floating-point		== strtod(,)
	 *		s	whitespace-delimited string
	 *		[	complicated string-scanner frob	]
	 *		c	one or more characters
	**		p	pointer representation
	**		n	return chars read thus far
	 *		%	single percent-sign
	 */
	/*
	 *	check for assignment flag.
	 */
	if ((c = *++sf.format) != '*')
	    sf.assign = TRUE;
	else {
	    sf.assign = FALSE;
	    c = *++sf.format;
	}

	/*
	 *	check for maximum field width specification.  must be positive,
	 *	if given (can't be negative, since the syntax doesn't allow a
	 *	way to denote it)
	 */
	if (isdigit(c)) {
	    sf.width = c - '0';
	    while (isdigit(c = *++sf.format))
		sf.width = sf.width * 10 + c - '0';
	    if (!sf.width) break;		/* has to be positive */
	} else sf.width = -1;		/* -1 means unspecified */

	/*
	 * now check for a size specification, 'h' for short, 'l' for long,
	 * 'L' for long double, nothing for normal.
	 */
	switch (c) {
	    case 'h':	sf.size = SHORT; c = *++sf.format; break;
	    case 'l':	sf.size = LONG; c = *++sf.format; break;
	    case 'L':	sf.size = LNGDBL; c = *++sf.format; break;
	    default:	sf.size = NORMAL;
	}

	/*
	 *	now find out what it is they really want to do.  each routine
	 *	clears sf.error if its parse succeeds.  for an unknown op,
	 *	the error flag remains set, so the code falls out the bottom
	 *	of the switch.
	 */
	sf.error = TRUE;		/* initialize error-on-parse flag */

	switch (c) {
	case 'd': duox_scanf(&sf, 1, 10);	break;	/* Decimal */
	case 'i': duox_scanf(&sf, 0, 0);	break;	/* Integer */
	case 'o': duox_scanf(&sf, 0, 8);	break;	/* Octal */
	case 'u': duox_scanf(&sf, 0, 10);	break;	/* Unsigned decimal */
	case 'x':
	case 'X': duox_scanf(&sf, 0, 16);	break;	/* Hexadecimal */
	case 'e':
	case 'E':
	case 'g':
	case 'G':
	case 'f': f_scanf(&sf);		break;	/* Floating-point */
	case 's': s_scanf(&sf);		break;	/* String */
	case '[': bracket_scanf(&sf);	break;	/* Scanset frob ] */
	case 'c': c_scanf(&sf);		break;	/* Char(s) */
	case 'p': duox_scanf(&sf,0,8);	break;	/* Pointer rep */
	case 'n': n_scanf(&sf);		break;	/* Ret # chars read */
	case '%':
	    sf.error = (getc(sf.stream) != '%');
	    sf.ccnt++;
	    break;
	}

	/* If any error in the parsed conversion, punt now. */
	if (sf.error) break;
    }

    if (!sf.asscnt && feof(sf.stream))	/* if got EOF before making any */
	return EOF;			/* assignments, return EOF, else */
    else return sf.asscnt;		/* return # of successful assigns */
}
/* N_SCANF - 'n' Return # of characters read thus far
*/
static void
n_scanf(sf)
register struct scanf_work *sf;
{
    switch (sf->size) {
    case SHORT:	 *(va_arg(sf->args, short *)) = sf->ccnt;	break;
    case NORMAL: *(va_arg(sf->args, int   *)) = sf->ccnt;	break;
    case LONG:	 *(va_arg(sf->args, long  *)) = sf->ccnt;	break;
    }
}
/* DUOX_SCANF - 'd', 'u', 'o', 'x' Scanner for various integers.
**	Similar functionality to strtol().
*/

static void duox_scanf(sf, signflag, base)
int signflag, base;
struct scanf_work *sf;
{
    long v = 0;				/* where the value will accumulate */
    unsigned long uv = 0;
    int negate = FALSE;			/* TRUE if should negate value */
    int owidth = sf->width;		/* remember field width at start */
    int c;

    while (isspace(c = getc(sf->stream)))	/* Skip whitespace */
	sf->ccnt++;			/* Char gobbled, account for it */


    switch (c) {
	case '-': negate = TRUE;	/* fall into '+' code */
	case '+': if (--sf->width == 0)	/* OK to gobble it, so bump cnt */
			return;		/* Match failure, no number */
		  c = getc(sf->stream);
     }

    /* Check for initial char '0' in case hex or not sure of base yet.
    ** If only "0x" is found without a valid hex digit following it, we
    ** must return a matching error since we can't back up more than 1
    ** character.  This is a strict interpretation of the dpANS.
    ** the behavior is different from what strtol() does since strtol
    ** operates on a string and can thus do infinite backup.
    */
    if (c == '0' && (!base || base == 16)) {
	if (--sf->width			/* Mark '0' gobbled */
	  && ((c = getc(sf->stream)) == 'x' || c == 'X')) {
	    if (!base) base = 16;
	    sf->width--;		/* Gobble the following 'X' */
	    c = getc(sf->stream);
 	} else {
	    sf->error = FALSE;		/* Got something (0 digit) */
	    if (!base) base = 8;
	}
    } else if (!base) base = 10, signflag++;

    for (; c != EOF && sf->width;
		((--sf->width) ? (c = getc(sf->stream)) : 0),
		sf->error = FALSE) {
	switch (base) {
	case 8:	if (!isodigit(c)) break;
		uv = (uv << 3) + (c - '0');
		continue;
	case 16: if (!isxdigit(c)) break;
		uv = (uv << 4) + toint(c);
		continue;
	case 10: if (!isdigit(c)) break;
		if (signflag) v = (v*10) + (c - '0');
		else uv = (uv*10) + (c - '0');
		continue;
	}
	ungetc(c, sf->stream);		/* Not a valid digit, stop loop */
	break;				/* and don't mark that char gobbled */
    }

    if (sf->error) return;

    sf->ccnt += (sf->width - owidth);	/* Account for # chars gobbled */
    if (sf->assign) {
	sf->asscnt++;			/* bump successful assignment count */
	if (!signflag) v = uv;
	if (negate) v = -v;		/* take care of that unary minus */
	switch (sf->size) {		/* assign to the right type now... */
	    case SHORT:  *va_arg(sf->args, short *) = v; break;
	    case NORMAL: *va_arg(sf->args, int   *) = v; break;
	    case LONG:   *va_arg(sf->args, long  *) = v; break;
	}
    }
}
/*
 *	floating-point scanner;  this would be nicer as separate
 *	steps; get a sign, get some digits, maybe a dot, maybe more
 *	digits, then maybe an e, maybe a sign, etc etc etc, but having
 *	to track the field width makes it a bitch, so for now i'm
 *	resorting to a single get-char loop with flags saying what
 *	field we're in, so there's a single place to watch the total
 *	# characters read so far...   (but there must be a better way)
 */

static void f_scanf(sf)
struct scanf_work *sf;
{
    int owidth = sf->width;		/* remember field width at start */
    double value, multiplier;
    int exponent, negate, exp_neg, post_dot, in_exponent, c;

    negate = exp_neg = post_dot = in_exponent = FALSE;
    exponent = value = 0;

    while (isspace(c = getc(sf->stream)))	/* Skip whitespace */
	sf->ccnt++;			/* Char gobbled, account for it */

    switch (c) {
	case '-': negate = TRUE;	/* fall into '+' code */
	case '+': if (--sf->width == 0)	/* OK to gobble it, so bump cnt */
			return;		/* Match failure, no number */
		  c = getc(sf->stream);
     }

    /* Get integer part if any */
    while (isdigit(c) && sf->width) {
	sf->error = FALSE;
	value = value * 10.0 + (c - '0');
	if (--sf->width) c = getc(sf->stream);
    }
    /* Get fractional part if any */
    if (c == '.' && sf->width) {
	if (--sf->width) c = getc(sf->stream);
	multiplier = 1.0;
	while (isdigit(c) && sf->width) {
	    sf->error = FALSE;
	    value += (c - '0') / (multiplier *= 10.0);
	    if (--sf->width) c = getc(sf->stream);
	}
    }
    /* Get exponent if any */
    if (sf->error) {		/* If no number yet, just return. */
	if (sf->width) ungetc(c, sf->stream);
	return;
    }
    if (sf->width && (c == 'e' || c == 'E')) {
	sf->error = TRUE;		/* Default to error again */
	if (--sf->width) switch (c = getc(sf->stream)) {
	    case '-':	exp_neg = TRUE;	/* fall into '+' code */
	    case '+':	if (--sf->width) c = getc(sf->stream);
			break;
	} else return;			/* Field truncated too soon, fail */
	while (isdigit(c) && sf->width) {
	    sf->error = FALSE;
	    exponent = exponent * 10 + c - '0';
	    if (--sf->width) c = getc(sf->stream);
	}
    }
    if (sf->width)			/* Push char back if stopped due to */
	ungetc(c, sf->stream);		/* mismatch rather than field */
    if (sf->error) return;

    sf->ccnt += (sf->width - owidth);	/* Account for # chars gobbled */

    for (; exponent > 0; exponent--) {
	if (exp_neg)	value /= 10.0;
	else		value *= 10.0;
    }
    if (sf->assign) {
	sf->asscnt++;			/* bump successful assignment count */
	if (negate) value = -value;
	switch (sf->size) {
	    case LONG:
		*((double *) va_arg(sf->args, double *)) = (double) value;
		break;
	    default:
		*((float *) va_arg(sf->args, float *)) = (float) value;
		break;
	}
    }    
}
/*
 *	character scanner
 */

static void c_scanf(sf)
struct scanf_work *sf;
{
    int c;
    char *s;

    if (sf->width < 0) {
	if ((c = getc(sf->stream)) != EOF) {
	    sf->error = FALSE;
	    sf->ccnt++;
	    if (sf->assign) {
		*((int *) va_arg(sf->args, int)) = c;
	    	sf->asscnt++;
	    }
	}
    } else {
	if (sf->assign)			/* make a pointer to their array */
	    s = va_arg(sf->args, char *);
	while (sf->width-- && (c = getc(sf->stream)) != EOF) {
	    sf->ccnt++;
	    if (sf->assign) *s++ = c;	/* suck the chars */
	}
	if (sf->width < 0) {		/* if scanned everything, then */
	    sf->error = FALSE;		/* flag that parse was OK */
	    if (sf->assign) sf->asscnt++;
	}
    }
}
/*
 *	string scanner
 */

static void s_scanf(sf)
struct scanf_work *sf;
{
    int owidth = sf->width;
    int c;
    char *s;

    while (isspace(c = getc(sf->stream)))	/* Skip whitespace */
	sf->ccnt++;			/* Char gobbled, account for it */

    if (sf->assign)
	s = va_arg(sf->args, char *);

    while (c != EOF && sf->width) {
	if (!isspace(c)) {
	    if (sf->assign) *s++ = c;
	    sf->error = FALSE;
	    if (--sf->width) c = getc(sf->stream);
	} else {
	    ungetc(c, sf->stream);
	    break;
	}
    }

    if (sf->error) return;

    sf->ccnt += (sf->width - owidth);	/* Account for # chars gobbled */
    if (sf->assign) {
	*s = '\0';
	sf->asscnt++;
    }
}
static void bracket_scanf(sf)
struct scanf_work *sf;
{
    int c, i, logic;
    char *s;

    if ((c = *++sf->format) == '^') {
	logic = FALSE;
	c = *++sf->format;
    } else if (c)				/* must be SOMETHING */
	logic = TRUE;
    else return;

    for (i = 0; i < CHAR_SET_SIZE; i++)		/* initialize the screen */
	valid_char[i] = !logic;

    while (c != ']') {
	valid_char[c%CHAR_SET_SIZE] = logic;
	if (!(c = *++sf->format)) return;
    }

    s = va_arg(sf->args, char *);		/* point to destination buf */

    while (sf->width-- && (c = getc(sf->stream)) != EOF) {
	if (c < CHAR_SET_SIZE && valid_char[c]) {
	    if (sf->assign) *s++ = c;
	    sf->ccnt++;
	} else {
	    ungetc(c, sf->stream);
	    break;
	}
    }

    sf->error = FALSE;		/* null strings and everything A-OK */

    if (sf->assign) {
	*s = '\0';
	sf->asscnt++;
    }
}