/* scan.c -- scanf and sscanf */
#include <ctype.h>
#include <stdarg.h>
#include "rts.h"
static int scanToken (), scanInteger (), scanReal (), scanPointer ();
static int scanTokenFromSet ();
#define BACKCH(c,f,S,s) (S ? ((s>DATA (S)) ? (s)-- : 0) : (char*)ungetc (c, f))
#define NEXTCH(l,f,S,s) (S ? ((*s=='\0') ? EOF : * (Char*)(s)++) : INCH (l, f))
#define SCAN_LEN 50
#define ANSI_LIMIT 512
/*
* mpd_scanf (locn, fp, sarg, fmt, argt, arg1...) -- handle scanf and sscanf
*
* locn source code location, for traceback
* fp file pointer for scanf; null for sscanf
* sarg result string for sscanf; null for scanf
* fmt scanning format
* argt string indicating argument types
* arg1... pointers to arguments
*
* This implements a mind-boggling number of input formats:
* o, q : octal integer only (may or may not end with q or Q)
* d, u : decimal integer only
* x : hexadecimal integer only (may or may not end with x or X)
* i : integer type is determined by input. if ends in q or Q
* then it is octal. ending in x or X means hex.
* otherwise it is decimal. error if hex digits are read
* and the literal does not end with x or X.
* c : read chars without skipping white space, default field width
* is one.
* s : reads string separated by white space. will read until
* a space character is read or the limit of the target
* string is encountered.
* e, f, g : reals.
* p : reads eight hex chars, default field width is eight.
* accepts the special string "==null==".
* b : boolean. Acceptable string are "true", "TRUE", "false" or
* "FALSE". The default field width is to read for one of
* above strings (4 or 5 chars). If a field width is specified,
* scan stops after field width chars are read and the scanned
* input is compared against one of the acceptable strings.
*
* field specifiers are of the form: %[*][digits]$
* where the digits are optional, and $ is one of the above formats.
* The optional '*' indicates suppression: the input will be read but not
* assigned. Even if a field is suppressed the input is checked as if it
* will be assigned. Field width must be greater than 0 and less than
* or equal to ANSI_LIMIT (512). In addition, the number of digits
* permitted for a field width is SCAN_LEN (50).
*
* This function returns when it reaches EOF or if any conversion fails.
* Upon reaching EOF the number of successful conversions is returned
* (or EOF). The number of successful conversions is returned otherwise.
*/
int
mpd_scanf (char *locn, FILE *fp, String *sarg, String *sfmt, Ptr argt, ...)
{
va_list ap;
Array *a;
String *sp;
Real *rp, real;
Ptr *pp, pointer;
Bool *bp, b;
Char *cp;
int *ip;
long lp;
int i, l, n, suppress, fldWidth, hasFldWidth, nread = 0;
char *fmt;
char c;
int ch;
char *s, *inString;
char buf[ANSI_LIMIT + 1];
char charset[256];
int in_set;
mpd_check_stk (CUR_STACK);
va_start (ap, argt);
if (sarg) {
inString = DATA (sarg); /* get string address */
inString[sarg->length] = '\0'; /* terminate with \0 */
PRIV (io_handoff) = 0;
} else {
BEGIN_IO (fp);
CHECK_FILE (locn, fp, EOF);
}
fmt = DATA (sfmt);
fmt[sfmt->length] = '\0'; /* terminate format with \0 */
/*
* Main Loop.
*/
c = *fmt++;
while (c != '\0') {
if (c == '%') {
/*
* if this char is a %, then this is the beginning of a
* field specifier.
*/
c = *fmt++;
if (c == '%') { /* read the literal % from the input */
ch = NEXTCH (locn, fp, sarg, inString);
if (ch != c)
if (ch == EOF && nread == 0)
IO_RETURN (fp, EOF);
else
IO_RETURN (fp, nread);
c = *fmt++;
continue;
} else if (c == '*') {
suppress = 1;
c = *fmt++;
} else
suppress = 0;
if (isdigit (c)) {
/*
* Read the optional field width
*/
buf[0] = c;
/*
* even though buf will not overflow until n > 512, stop
* after 50. this is consistent with mpd_read ().
*/
for (n = 1; isdigit (c = *fmt++) && n < SCAN_LEN; n++)
buf[n] = c;
if (n >= SCAN_LEN)
ABORT_IO (fp, locn, "field width specifier too long");
buf[n] = '\0';
n = sscanf (buf, "%d", &fldWidth);
if (n != 1)
ABORT_IO(fp,locn,"field width specifier conversion failed");
if (fldWidth > ANSI_LIMIT)
ABORT_IO (fp, locn, "field width too large");
else if (fldWidth <= 0)
ABORT_IO (fp,locn,"field width must be greater than zero");
hasFldWidth = 1; /* this is needed for b, c & p */
} else {
if (*argt == 'c' && !suppress)
fldWidth = 1;
else
fldWidth = ANSI_LIMIT;
hasFldWidth = 0; /* this is needed for b, c & p */
}
switch (c) {
case '\0':
ABORT_IO (fp, locn, "premature termination of format string");
break;
case 'o':
c = 'q';
/* NO BREAK */
case 'q':
case 'd':
case 'u':
case 'x':
case 'i':
/*
* all the integer cases are handled here.
*/
n = scanInteger (locn, fp, sarg, &inString, buf, fldWidth, &c);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
if (!suppress) {
/*
* if this field is not suppressed then get the next
* argument. It should be a pointer to an integer.
*/
if (*argt != 'i')
ABORT_IO (fp, locn,
"arguments do not match format string");
ip = va_arg (ap, int *);
++argt;
if (c == 'q')
n = sscanf (buf, "%o", &i);
else if (c == 'x')
n = sscanf (buf, "%x", &i);
else
n = sscanf (buf, "%d", &i);
if (n == 1) {
*ip = i;
++nread;
} else
IO_RETURN (fp, nread);
}
break; /*---- end of integer conversion */
case '[':
/*
* Handle the charset (%[...]) format type here.
* First, build the charset table.
*/
if (*fmt == '^') {
in_set = 0;
fmt++;
} else
in_set = 1;
memset (charset, !in_set, sizeof (charset));
s = fmt;
while (((c = *fmt++) != ']') || (fmt == s + 1)) {
if (c == '\0')
ABORT_IO (fp, locn,
"premature termination of format string");
if (*fmt == '-') {
fmt++;
n = *fmt++ & 0xFF;
if (n == '\0')
ABORT_IO (fp, locn,
"premature termination of format string");
for (i = c & 0xFF; i <= n; i++)
charset[i] = in_set;
} else {
charset[c & 0xFF] = in_set;
}
}
/*
* The rest of this is similar to %s handling.
*/
if (fldWidth == 1 && *argt == 'c' && !suppress) {
/*
* do this only if format is '%1s', arg is char AND
* the field is NOT suppressed.
*/
ch = NEXTCH (locn, fp, sarg, inString);
if (ch == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (!charset[ch])
IO_RETURN (fp, nread ? nread : EOF);
cp = va_arg (ap, Char *);
*cp = ch;
++nread;
++argt;
} else if (*argt == 'a' && !suppress) {
/*
* scan into array of characters
*/
++argt;
a = (Array *) va_arg (ap, Array *);
l = UB (a, 0) - LB (a, 0) + 1;
/*
* Scan max of (length of string, field width)
*/
i = (l < fldWidth) ? l : fldWidth;
if ((n = scanTokenFromSet
(locn, fp, sarg, &inString, buf, i, charset)) == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
++nread;
strncpy (ADATA (a), buf, n);
/*
* blank out the remainder of the input array
*/
while (n < l)
ADATA (a) [n++] = ' ';
} else {
n = scanTokenFromSet (locn, fp, sarg, &inString, buf,
fldWidth, charset);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
if (!suppress) {
if (*argt != 's')
ABORT_IO (fp, locn,
"arguments do not match format string");
sp = (String *) va_arg (ap, String *);
++argt;
/*
* set length limit (l) to lesser of
* size of String or or the number of chars read.
*/
l = MAXLENGTH (sp);
l = (l < n) ? l : n;
strncpy (DATA (sp), buf, l);
sp->length = l;
++nread;
}
}
break; /*---- end of charset conversion */
case 's':
/*
* handle the string (s) format type here.
* if this field is suppressed then do not consume an arg.
* have to special case the format %1s to allow assignment
* to a char.
*/
if (fldWidth == 1 && *argt == 'c' && !suppress) {
/*
* do this only if format is '%1s', arg is char AND
* the field is NOT suppressed.
*/
/* consume white space */
while (isspace (ch = NEXTCH (locn, fp, sarg, inString)))
;
if (ch == EOF)
IO_RETURN (fp, nread ? nread : EOF);
cp = va_arg (ap, Char *);
*cp = ch;
++nread;
++argt;
} else if (*argt == 'a' && !suppress) {
/*
* scan into array of characters
*/
++argt;
a = (Array *) va_arg (ap, Array *);
l = UB (a, 0) - LB (a, 0) + 1;
/*
* Scan no more than max (length of string, field width)
*/
i = (l < fldWidth) ? l : fldWidth;
if ((n = scanToken (locn,fp,sarg,&inString,buf,i)) == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
++nread;
strncpy (ADATA (a), buf, n);
/*
* blank out the remainder of the input array
*/
while (n < l)
ADATA (a) [n++] = ' ';
} else {
n = scanToken (locn, fp, sarg, &inString, buf, fldWidth);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
if (!suppress) {
if (*argt != 's')
ABORT_IO (fp, locn,
"arguments do not match format string");
sp = (String *) va_arg (ap, String *);
++argt;
/*
* set length limit (l) to lesser of
* size of String or or the number of chars read.
*/
l = MAXLENGTH (sp);
l = (l < n) ? l : n;
strncpy (DATA (sp), buf, l);
sp->length = l;
++nread;
}
}
break; /*---- end of string format conversion */
case 'c':
if (!hasFldWidth)
fldWidth = 1;
if (suppress) {
for (i = 0; i < fldWidth; ++i) {
if (ch == EOF)
IO_RETURN (fp, nread ? nread : EOF);
ch = NEXTCH (locn, fp, sarg, inString);
}
break;
}
if (*argt == 'c') {
if (hasFldWidth && fldWidth != 1)
ABORT_IO (fp, locn,
"invalid field width for char variable");
cp = va_arg (ap, Char *);
ch = NEXTCH (locn, fp, sarg, inString);
if (ch == EOF)
IO_RETURN (fp, nread ? nread : EOF);
*cp = ch;
++argt;
++nread;
} else if (*argt == 'a') {
char *p;
++argt;
a = (Array *) va_arg (ap, Array *);
p = ADATA (a);
l = UB (a, 0) - LB (a, 0) + 1;
if (l == 0)
break; /* empty array */
i = (l < fldWidth) ? l : fldWidth;
for (n = 0; n < i; ++n) {
ch = NEXTCH (locn, fp, sarg, inString);
if (ch == EOF)
break;
buf[n] = ch;
}
if (n == 0) /* must have reached EOF */
IO_RETURN (fp, nread ? nread : EOF);
++nread;
strncpy (p, buf, n);
if (n < l) {
s = p;
s += n;
for (; n < l; n++, *s++ = ' ')
;
}
if (ch == EOF)
IO_RETURN (fp, nread); /* nread IS greater than 0 */
} else if (*argt == 's') {
sp = (String *) va_arg (ap, String *);
++argt;
l = MAXLENGTH (sp);
i = (l < fldWidth) ? l : fldWidth;
for (n = 0; n < i; ++n) {
ch = NEXTCH (locn, fp, sarg, inString);
if (ch == EOF)
break;
buf[n] = ch;
}
if (n == 0) /* must have reached EOF */
IO_RETURN (fp, nread ? nread : EOF);
strncpy (DATA (sp), buf, n);
sp->length = n;
++nread;
if (ch == EOF)
IO_RETURN (fp, nread); /* nread IS greater than zero */
} else
ABORT_IO (fp, locn,"arguments do not match format string");
break;
case 'e':
case 'f':
case 'g':
n = scanReal (locn, fp, sarg, &inString, buf, fldWidth);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
if (!suppress) {
if (*argt != 'r')
ABORT_IO (fp, locn,
"arguments do not match format string");
rp = va_arg (ap, Real *);
++argt;
i = sscanf (buf, "%lf", &real);
if (i == 1) {
*rp = real;
++nread;
} else
IO_RETURN (fp, nread);
}
break;
case 'b':
/*
* if a field width has been specified then read exactly
* fldWidth chars. otherwise read 4 if true and 5 if false.
*/
if (hasFldWidth) {
n = scanToken (locn, fp, sarg, &inString, buf, fldWidth);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n != fldWidth)
IO_RETURN (fp, nread);
else if (strncmp (buf, "true ", fldWidth) == 0 ||
strncmp (buf, "TRUE ", fldWidth) == 0)
b = TRUE;
else if (strncmp (buf, "false", fldWidth) == 0 ||
strncmp (buf, "FALSE", fldWidth) == 0)
b = FALSE;
else
IO_RETURN (fp, nread);
} else {
n = scanToken (locn, fp, sarg, &inString, buf, 4);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n != 4)
IO_RETURN (fp, nread);
else if (strncmp (buf, "true", 4) == 0 ||
strncmp (buf, "TRUE", 4) == 0)
b = TRUE;
else if (strncmp (buf, "fals", 4) == 0 ||
strncmp (buf, "FALS", 4) == 0) {
if ((ch = NEXTCH (locn, fp, sarg, inString)) == EOF)
IO_RETURN (fp, (nread > 0) ? nread : EOF);
if (ch == 'e' && buf[0] == 'f')
b = FALSE;
else if (ch == 'E' && buf[0] == 'F')
b = FALSE;
else
IO_RETURN (fp, nread);
} else
IO_RETURN (fp, nread);
}
if (!suppress) {
if (*argt != 'b')
ABORT_IO (fp, locn,
"arguments do not match format string");
bp = va_arg (ap, Bool *);
*bp = b;
++nread;
++argt;
}
break; /*---- end of bool conversion */
case 'p':
/*
* default for pointer field is eight.
*/
if (!hasFldWidth)
fldWidth = 8;
n = scanPointer (locn, fp, sarg, &inString, buf, fldWidth);
if (n == EOF)
IO_RETURN (fp, nread ? nread : EOF);
if (n == 0)
IO_RETURN (fp, nread);
if (strcmp (buf, "==null==") == 0)
pointer = NULL;
else {
i = sscanf (buf, "%lx", &lp);
if (i != 1)
IO_RETURN (fp, nread);
pointer = (Ptr) lp;
}
if (!suppress) {
if (*argt != 'p')
ABORT_IO (fp, locn,
"arguments do not match format string");
pp = va_arg (ap, Ptr *);
++argt;
*pp = pointer;
++nread;
}
break;
default:
ABORT_IO (fp, locn, "invalid field specifier in format string");
} /* switch */
} else {
/*
* format char is not a '%'.
* consume the next char in the input. if it doesn't match
* the format char exactly then return.
*/
ch = NEXTCH (locn, fp, sarg, inString);
if (c != ch)
IO_RETURN (fp, nread);
}
if (c != '\0')
c = *fmt++;
} /* while */
/* check and see if there were too many args passed */
if (*argt != '\0')
ABORT_IO (fp, locn, "more arguments than fields in format string");
IO_RETURN (fp, nread);
}
/*
* Scan a token from either the file or string into the buffer.
* Token is any string of consecutive non-white chars.
* Skips leading white space and scans up to len chars into buf.
*
* This is always called between a BEGIN_IO and an END_IO so we
* do not do it here.
*/
static int
scanToken (locn, fp, S, s, buf, len)
char *locn;
FILE *fp;
String *S;
char **s, *buf;
int len;
{
int n, ch;
/* skip white space */
while (isspace (ch = NEXTCH (locn, fp, S, *s)))
;
if (ch == EOF)
return EOF;
n = 0;
while (ch != EOF && !isspace (ch) && n < len) {
buf[n++] = ch;
ch = NEXTCH (locn, fp, S, *s);
}
BACKCH (ch, fp, S, *s);
buf[n] = '\0';
return n;
}
/*
* Scan a token from either the file or string into the buffer.
* Token is any string of chars from the charset supplied.
* Scans up to len chars into buf.
*
* This is always called between a BEGIN_IO and an END_IO so we
* do not do it here.
*/
static int
scanTokenFromSet (locn, fp, S, s, buf, len, charset)
char *locn;
FILE *fp;
String *S;
char **s, *buf;
int len;
char *charset;
{
int n, ch;
ch = NEXTCH (locn, fp, S, *s);
if (ch == EOF)
return EOF;
n = 0;
while (ch != EOF && charset[ch] && n < len) {
buf[n++] = ch;
ch = NEXTCH (locn, fp, S, *s);
}
BACKCH (ch, fp, S, *s);
buf[n] = '\0';
return n;
}
/*
* Read characters that make an integer literal into buf (without conversion).
* Return the number of characters accepted.
*
* len is the maximum field width.
*
* type tells what kind of integer is expected (d, q, x or i).
* In the case of 'i' will accept 'd' or 'x'. type is set to 'x' if a hex
* digit is encountered. If "123abc" is input for type 'i', then assign
* 123 to int and put back the c. Type 'u' cannot begin with a '-';
* all other types may have an optional sign.
*
* This is always called between a BEGIN_IO and an END_IO so we
* do not do it here.
*/
static int
scanInteger (locn, fp, S, s, buf, len, type)
char *locn;
FILE *fp;
String *S;
char **s, *buf;
int len;
char *type;
{
int n, ch;
int need_x = 0;
/* skip white space */
while (isspace (ch = NEXTCH (locn, fp, S, *s)))
;
if (ch == EOF)
return EOF;
/* check for leading sign */
n = 0;
if (ch == '-') {
if (*type == 'u') {
BACKCH (ch, fp, S, *s);
return 0;
}
buf[n++] = ch;
} else if (ch == '+') {
buf[n++] = ch;
} else {
BACKCH (ch, fp, S, *s);
}
/* scan the digits */
while (n < len && (ch = NEXTCH (locn, fp, S, *s)) != EOF) {
if (ch >= '0' && ch <= '7') {
/* okay for all formats */
buf[n++] = ch;
} else if (ch == '8' || ch == '9') {
/* not okay for octal (q) format */
if (*type == 'q') {
BACKCH (ch, fp, S, *s);
break;
} else {
buf[n++] = ch;
}
} else if ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
/* only okay for hex (x) and i formats */
if (*type == 'i') {
*type = 'x';
need_x = 1;
}
if (*type == 'x') {
buf[n++] = ch;
} else {
BACKCH (ch, fp, S, *s);
break;
}
} else if ((ch == 'x' || ch == 'X') && (*type == 'x' || *type == 'i')) {
*type = 'x';
break;
} else if ((ch == 'q' || ch == 'Q') && (*type == 'q' || *type == 'i')) {
*type = 'q';
break;
} else {
BACKCH (ch, fp, S, *s); /* put back readahead char */
break;
}
}
/*
* if started as 'i' format and read hex chars then must have
* an 'x' or 'X' at the end.
*/
if (need_x && (ch != 'x' && ch != 'X')) {
BACKCH (ch, fp, S, *s);
return 0;
}
buf[n] = '\0'; /* terminate the string */
return n; /* return its length */
}
/*
* Scan a real literal. Consumes leading white space, an optional sign, etc.
* Stops scanning at first character that cannot belong to a real.
* Copies scanned characters (less white space) into buf.
* Scans no more than len chars. Returns length of string if successful,
* zero otherwise.
*
* This is always called between a BEGIN_IO and an END_IO so we
* do not do it here.
*/
static int
scanReal (locn, fp, S, s, buf, len)
char *locn;
FILE *fp;
String *S;
char **s, *buf;
int len;
{
int n, ch;
int intpart = 0; /* integer part seen? */
int decimal = 0; /* decimal point seen? */
/* consume white space */
while (isspace (ch = NEXTCH (locn, fp, S, *s)))
;
if (ch == EOF)
return EOF;
n = 0;
/* save optional sign char */
if (ch == '-' || ch == '+') {
buf[n++] = ch;
ch = NEXTCH (locn, fp, S, *s);
}
if (n >= len)
return 0;
/* save all digits */
if (isdigit (ch))
intpart = 1;
while (isdigit (ch)) {
buf[n++] = ch;
if (n >= len) {
buf[n] = '\0';
return n;
}
ch = NEXTCH (locn, fp, S, *s);
}
if (n >= len) {
buf[n] = '\0';
return n;
}
/* can have a decimal followed by more digits */
if (ch == '.') {
decimal = 1;
buf[n++] = ch;
ch = NEXTCH (locn, fp, S, *s);
if (n >= len)
return 0;
if (!isdigit (ch) && !intpart)
return 0;
while (isdigit (ch)) {
buf[n++] = ch;
if (n >= len) {
buf[n++] = '\0';
return n;
}
ch = NEXTCH (locn, fp, S, *s);
}
}
/* can have an exponent */
if ((ch == 'e' || ch == 'E')) {
buf[n++] = ch;
if (n >= len)
return 0;
ch = NEXTCH (locn, fp, S, *s);
if ((ch == '-' || ch == '+')) {
buf[n++] = ch;
if (n >= len)
return 0;
ch = NEXTCH (locn, fp, S, *s);
}
if (!isdigit (ch)) { /* must have at least one digit in exponent */
if (decimal) {
BACKCH (ch, fp, S, *s);
buf[n] = '\0';
return n;
} else
return 0;
}
while (isdigit (ch)) {
buf[n++] = ch;
if (n >= len) {
buf[n] = '\0';
return n;
}
ch = NEXTCH (locn, fp, S, *s);
}
}
/* an extra char is ALWAYS read */
if (ch != EOF)
BACKCH (ch, fp, S, *s);
buf[n] = '\0';
return n;
}
/*
* Scan for a pointer. Skips leading white space. Accepts special
* string "==null==" for the NULL pointer.
*
* This is always called between a BEGIN_IO and an END_IO so we
* do not do it here.
*/
#define isptrchar(c) (isdigit (c) || (c>='a'&&c<='f') || (c>='A'&&c<='F'))
static int
scanPointer (locn, fp, S, s, buf, len)
char *locn;
FILE *fp;
String *S;
char **s, *buf;
int len;
{
int n, ch;
/* skip white space */
while (isspace (ch = NEXTCH (locn, fp, S, *s)))
;
if (ch == EOF)
return EOF;
/* check for special string '==null==' */
if (ch == '=') {
for (n = 0; n < len; ++n) {
if (isspace (ch))
break;
buf[n] = ch;
ch = NEXTCH (locn, fp, S, *s);
}
BACKCH (ch, fp, S, *s);
} else {
for (n = 0; n < len; ++n) {
if (!isptrchar (ch))
break;
buf[n] = ch;
ch = NEXTCH (locn, fp, S, *s);
}
if (ch != 'x' && ch != 'X')
BACKCH (ch, fp, S, *s);
}
buf[n] = '\0';
return n;
}
syntax highlighted by Code2HTML, v. 0.9.1