ack/lang/cem/libcc.ansi/core/scanf/doscan.c
2019-06-15 13:53:20 +02:00

547 lines
10 KiB
C

/*
* doscan.c - scan formatted input
*/
/* $Id$ */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include "doscan.h"
#if ACKCONF_WANT_STDIO
#if _EM_WSIZE == _EM_PSIZE
#define set_pointer(flags) /* nothing */
#elif _EM_LSIZE == _EM_PSIZE
#define set_pointer(flags) (flags |= FL_LONG)
#else
#error garbage pointer size
#define set_pointer(flags) /* compilation might continue */
#endif
#define NUMLEN 512
#define NR_CHARS 256
static char Xtable[NR_CHARS];
static char inp_buf[NUMLEN];
int (*_doscan_get)(void);
void (*_doscan_unget)(int c);
/* Collect a number of characters which constitite an ordinal number.
* When the type is 'i', the base can be 8, 10, or 16, depending on the
* first 1 or 2 characters. This means that the base must be adjusted
* according to the format of the number. At the end of the function, base
* is then set to 0, so strtol() will get the right argument.
*/
static char* o_collect(register int c, char type, int width, int* basep)
{
register char* bufp = inp_buf;
register int base;
switch (type)
{
case 'i': /* i means octal, decimal or hexadecimal */
case 'p':
case 'x':
case 'X':
base = 16;
break;
case 'd':
case 'u':
base = 10;
break;
case 'o':
base = 8;
break;
case 'b':
base = 2;
break;
}
if (c == '-' || c == '+')
{
*bufp++ = c;
if (--width)
c = _doscan_get();
}
if (width && c == '0' && base == 16)
{
*bufp++ = c;
if (--width)
c = _doscan_get();
if (c != 'x' && c != 'X')
{
if (type == 'i')
base = 8;
}
else if (width)
{
*bufp++ = c;
if (--width)
c = _doscan_get();
}
}
else if (type == 'i')
base = 10;
while (width)
{
if (((base == 10) && isdigit(c))
|| ((base == 16) && isxdigit(c))
|| ((base == 8) && isdigit(c) && (c < '8'))
|| ((base == 2) && isdigit(c) && (c < '2')))
{
*bufp++ = c;
if (--width)
c = _doscan_get();
}
else
break;
}
if (width && c != EOF)
_doscan_unget(c);
if (type == 'i')
base = 0;
*basep = base;
*bufp = '\0';
return bufp - 1;
}
#if ACKCONF_WANT_STDIO_FLOAT
/* The function f_collect() reads a string that has the format of a
* floating-point number. The function returns as soon as a format-error
* is encountered, leaving the offending character in the input. This means
* that 1.el leaves the 'l' in the input queue. Since all detection of
* format errors is done here, _doscan() doesn't call strtod() when it's
* not necessary, although the use of the width field can cause incomplete
* numbers to be passed to strtod(). (e.g. 1.3e+)
*/
static char* f_collect(register int c, register int width)
{
register char* bufp = inp_buf;
int digit_seen = 0;
if (c == '-' || c == '+')
{
*bufp++ = c;
if (--width)
c = _doscan_get();
}
while (width && isdigit(c))
{
digit_seen++;
*bufp++ = c;
if (--width)
c = _doscan_get();
}
if (width && c == '.')
{
*bufp++ = c;
if (--width)
c = _doscan_get();
while (width && isdigit(c))
{
digit_seen++;
*bufp++ = c;
if (--width)
c = _doscan_get();
}
}
if (!digit_seen)
{
if (width && c != EOF)
_doscan_unget(c);
return inp_buf - 1;
}
else
digit_seen = 0;
if (width && (c == 'e' || c == 'E'))
{
*bufp++ = c;
if (--width)
c = _doscan_get();
if (width && (c == '+' || c == '-'))
{
*bufp++ = c;
if (--width)
c = _doscan_get();
}
while (width && isdigit(c))
{
digit_seen++;
*bufp++ = c;
if (--width)
c = _doscan_get();
}
if (!digit_seen)
{
if (width && c != EOF)
_doscan_unget(c);
return inp_buf - 1;
}
}
if (width && c != EOF)
_doscan_unget(c);
*bufp = '\0';
return bufp - 1;
}
#endif
/*
* the routine that does the scanning
*/
int _doscan(const char* format, va_list ap)
{
int done = 0; /* number of items done */
int nrchars = 0; /* number of characters read */
int conv = 0; /* # of conversions */
int base; /* conversion base */
unsigned long val; /* an integer value */
register char* str; /* temporary pointer */
char* tmp_string; /* ditto */
unsigned width; /* width of field */
int flags; /* some flags */
int reverse; /* reverse the checking in [...] */
int kind;
register int ic; /* the input character */
#if ACKCONF_WANT_STDIO_FLOAT
long double ld_val;
#endif
if (!*format)
return 0;
while (1)
{
if (isspace(*format))
{
while (isspace(*format))
format++; /* skip whitespace */
ic = _doscan_get();
nrchars++;
while (isspace(ic))
{
ic = _doscan_get();
nrchars++;
}
if (ic != EOF)
_doscan_unget(ic);
nrchars--;
}
if (!*format)
break; /* end of format */
if (*format != '%')
{
ic = _doscan_get();
nrchars++;
if (ic != *format++)
{
if (ic != EOF)
_doscan_unget(ic);
nrchars--;
break; /* error */
}
continue;
}
format++;
if (*format == '%')
{
ic = _doscan_get();
nrchars++;
if (ic == '%')
{
format++;
continue;
}
else
break;
}
flags = 0;
if (*format == '*')
{
format++;
flags |= FL_NOASSIGN;
}
if (isdigit(*format))
{
flags |= FL_WIDTHSPEC;
for (width = 0; isdigit(*format);)
width = width * 10 + *format++ - '0';
}
switch (*format)
{
case 'h':
flags |= FL_SHORT;
format++;
break;
case 'l':
flags |= FL_LONG;
format++;
break;
case 'L':
flags |= FL_LONGDOUBLE;
format++;
break;
}
kind = *format;
if ((kind != 'c') && (kind != '[') && (kind != 'n'))
{
do
{
ic = _doscan_get();
nrchars++;
} while (isspace(ic));
if (ic == EOF)
break; /* outer while */
}
else if (kind != 'n')
{ /* %c or %[ */
ic = _doscan_get();
if (ic == EOF)
break; /* outer while */
nrchars++;
}
switch (kind)
{
default:
/* not recognized, like %q */
return conv || (ic != EOF) ? done : EOF;
break;
case 'n':
if (!(flags & FL_NOASSIGN))
{ /* silly, though */
if (flags & FL_SHORT)
*va_arg(ap, short*) = (short)nrchars;
else if (flags & FL_LONG)
*va_arg(ap, long*) = (long)nrchars;
else
*va_arg(ap, int*) = (int)nrchars;
}
break;
case 'p': /* pointer */
set_pointer(flags);
/* fallthrough */
case 'b': /* binary */
case 'd': /* decimal */
case 'i': /* general integer */
case 'o': /* octal */
case 'u': /* unsigned */
case 'x': /* hexadecimal */
case 'X': /* ditto */
if (!(flags & FL_WIDTHSPEC) || width > NUMLEN)
width = NUMLEN;
if (!width)
return done;
str = o_collect(ic, kind, width, &base);
if (str < inp_buf
|| (str == inp_buf
&& (*str == '-'
|| *str == '+')))
return done;
/*
* Although the length of the number is str-inp_buf+1
* we don't add the 1 since we counted it already
*/
nrchars += str - inp_buf;
if (!(flags & FL_NOASSIGN))
{
if (kind == 'd' || kind == 'i')
val = strtol(inp_buf, &tmp_string, base);
else
val = strtoul(inp_buf, &tmp_string, base);
if (flags & FL_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)val;
else if (flags & FL_SHORT)
*va_arg(ap, unsigned short*) = (unsigned short)val;
else
*va_arg(ap, unsigned*) = (unsigned)val;
}
break;
case 'c':
if (!(flags & FL_WIDTHSPEC))
width = 1;
if (!(flags & FL_NOASSIGN))
str = va_arg(ap, char*);
if (!width)
return done;
while (width && ic != EOF)
{
if (!(flags & FL_NOASSIGN))
*str++ = (char)ic;
if (--width)
{
ic = _doscan_get();
nrchars++;
}
}
if (width)
{
if (ic != EOF)
_doscan_unget(ic);
nrchars--;
}
break;
case 's':
if (!(flags & FL_WIDTHSPEC))
width = 0xffff;
if (!(flags & FL_NOASSIGN))
str = va_arg(ap, char*);
if (!width)
return done;
while (width && ic != EOF && !isspace(ic))
{
if (!(flags & FL_NOASSIGN))
*str++ = (char)ic;
if (--width)
{
ic = _doscan_get();
nrchars++;
}
}
/* terminate the string */
if (!(flags & FL_NOASSIGN))
*str = '\0';
if (width)
{
if (ic != EOF)
_doscan_unget(ic);
nrchars--;
}
break;
case '[':
if (!(flags & FL_WIDTHSPEC))
width = 0xffff;
if (!width)
return done;
if (*++format == '^')
{
reverse = 1;
format++;
}
else
reverse = 0;
for (str = Xtable; str < &Xtable[NR_CHARS]; str++)
*str = 0;
if (*format == ']')
Xtable[*format++] = 1;
while (*format && *format != ']')
{
Xtable[*format++] = 1;
if (*format == '-')
{
format++;
if (*format
&& *format != ']'
&& *(format) >= *(format - 2))
{
int c;
for (c = *(format - 2) + 1; c <= *format; c++)
Xtable[c] = 1;
format++;
}
else
Xtable['-'] = 1;
}
}
if (!*format || !(Xtable[ic] ^ reverse))
{
if (ic != EOF)
_doscan_unget(ic);
return done;
}
if (!(flags & FL_NOASSIGN))
str = va_arg(ap, char*);
do
{
if (!(flags & FL_NOASSIGN))
*str++ = (char)ic;
if (--width)
{
ic = _doscan_get();
nrchars++;
}
} while (width && ic != EOF && (Xtable[ic] ^ reverse));
if (width)
{
if (ic != EOF)
_doscan_unget(ic);
nrchars--;
}
if (!(flags & FL_NOASSIGN))
{ /* terminate string */
*str = '\0';
}
break;
#if ACKCONF_WANT_STDIO_FLOAT
case 'e':
case 'E':
case 'f':
case 'g':
case 'G':
if (!(flags & FL_WIDTHSPEC) || width > NUMLEN)
width = NUMLEN;
if (!width)
return done;
str = f_collect(ic, width);
if (str < inp_buf
|| (str == inp_buf
&& (*str == '-'
|| *str == '+')))
return done;
/*
* Although the length of the number is str-inp_buf+1
* we don't add the 1 since we counted it already
*/
nrchars += str - inp_buf;
if (!(flags & FL_NOASSIGN))
{
ld_val = strtod(inp_buf, &tmp_string);
if (flags & FL_LONGDOUBLE)
*va_arg(ap, long double*) = (long double)ld_val;
else if (flags & FL_LONG)
*va_arg(ap, double*) = (double)ld_val;
else
*va_arg(ap, float*) = (float)ld_val;
}
break;
#endif
} /* end switch */
conv++;
if (!(flags & FL_NOASSIGN) && kind != 'n')
done++;
format++;
}
return conv || (ic != EOF) ? done : EOF;
}
#endif