Files
zSoft/libraries/umlibc/stdio/vfscanf.c

601 lines
14 KiB
C
Executable File

/* Copyright (c) 2002,2004,2005 Joerg Wunsch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/* $Id: vfscanf.c,v 1.13 2005/11/09 21:54:33 joerg_wunsch Exp $ */
#include <avr/pgmspace.h>
#include <ctype.h>
#include <stdint.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stdio_private.h"
/*
* This file can be compiled into more than one flavour. The default
* is to offer the usual modifiers and integer formatting support
* (level 2). Level 1 maintains a minimal version that just offers
* integer formatting, but no modifier support except "h" and "l".
* Level 3 is intented for floating point support, and also adds the
* %[ conversion.
*/
#ifndef SCANF_LEVEL
# define SCANF_LEVEL SCANF_STD
#endif
#if SCANF_LEVEL == SCANF_MIN || SCANF_LEVEL == SCANF_STD \
|| SCANF_LEVEL == SCANF_FLT
/* OK */
#else
# error "Not a known scanf level."
#endif
#if SCANF_LEVEL >= SCANF_MIN
#define FLHASPERCENT 0x01 /* first % found */
#define FLUNSIGNED 0x02 /* arg is unsinged (long) * */
#define FLLONG 0x04 /* arg is long int * */
#define FLMINUS 0x08 /* minus sign scanned */
#endif
#if SCANF_LEVEL >= SCANF_STD
#define FLSTAR 0x10 /* suppress assingment (* fmt) */
#endif
#if SCANF_LEVEL >= SCANF_FLT
#define FLBRACKET 0x20 /* %[ found, now collecting the set */
#define FLNEGATE 0x40 /* negate %[ set */
/* bit set macros for %[ format */
#define vscanf_set_bit(i) \
buf[(unsigned char)(i) / 8] |= (1 << (unsigned char)(i) % 8)
#define vscanf_bit_is_set(i) \
(buf[(unsigned char)(i) / 8] & (1 << (unsigned char)(i) % 8))
/*
* 40 Bytes is sufficient for 32-bit floating point values in `f'
* style notation. Note that this buffer is also used to record the
* bit vector for %[ formats, so it must be at least 256/8 = 32 bytes
* long.
*/
#define FLTBUF 40
#endif
int
vfscanf(FILE *stream, const char *fmt, va_list ap) {
union {
#if SCANF_LEVEL >= SCANF_FLT
double d;
#endif
unsigned long ul;
long l;
char *cp;
} a;
char c; /* holds a char from the format string */
uint8_t base;
int nconvs, rv, i, j, olen;
#if SCANF_LEVEL > SCANF_MIN
int8_t width;
#endif
uint8_t flags;
#if SCANF_LEVEL >= SCANF_FLT
char *bp;
char fltchars[] = "0123456789Ee.";
char buf[FLTBUF];
#endif
flags = 0;
nconvs = 0;
i = 0;
rv = EOF;
olen = stream->len = 0;
if ((stream->flags & __SRD) == 0)
return EOF;
/*
* Do not use fmt++ in the next line. pgm_read_byte() is a
* macro, so it could evaluate its argument more than once.
*/
while ((c = ((stream->flags & __SPGM)? pgm_read_byte(fmt): *fmt))) {
fmt++;
#if SCANF_LEVEL >= SCANF_FLT
if (flags & FLBRACKET) {
if (c == '^' && i == 0 && !(flags & FLNEGATE)) {
flags |= FLNEGATE; /* negate set */
continue; /* without bumping i */
}
if (c == '-') {
if (i == 0) {
addbit:
vscanf_set_bit(c);
i++;
continue;
}
flags |= FLMINUS;
i++;
continue;
}
if (c == ']') {
if (i == 0)
goto addbit;
if (flags & FLMINUS) /* trailing - before ] */
vscanf_set_bit('-');
if (flags & FLNEGATE)
for (i = 0; i < 256 / 8; i++)
buf[i] = ~buf[i];
if (!(flags & FLSTAR))
a.cp = va_arg(ap, char *);
while (width-- > 0) {
if ((i = getc(stream)) == EOF)
break;
if (!vscanf_bit_is_set(i)) {
ungetc(i, stream);
break;
}
if (!(flags & FLSTAR))
*a.cp++ = i;
}
if (!(flags & FLSTAR))
*a.cp = '\0';
goto nextconv;
}
if (flags & FLMINUS) {
flags &= ~FLMINUS;
while ((unsigned char)j < (unsigned char)c) {
vscanf_set_bit(j);
j++;
}
}
j = (unsigned char)c; /* remember for x-y range */
goto addbit;
}
#endif /* SCANF_LEVEL >= SCANF_FLT */
if (flags & FLHASPERCENT) {
if (c == '%') {
flags &= ~FLHASPERCENT;
#if SCANF_LEVEL > SCANF_MIN
goto literal;
#else
continue;
#endif
}
/*
* Modifiers go first. They all end up in a
* "continue" statement so to fetch the next
* char from the format string.
*/
#if SCANF_LEVEL > SCANF_MIN
if (c >= '0' && c <= '9') {
c -= '0';
if (width == SCHAR_MAX)
width = 0;
else
width *= 10;
width += c;
continue;
}
#endif /* SCANF_LEVEL > SCANF_MIN */
c = tolower(c);
switch (c) {
#if SCANF_LEVEL > SCANF_MIN
case '*':
flags |= FLSTAR;
continue;
#endif /* SCANF_LEVEL > SCANF_MIN */
case 'h':
//#if SHRT_MAX != INT_MAX
//#error "SHRT_MAX != INT_MAX for target: not supported"
//#endif
/*
* short int and int are identical on
* our target platform, ignore.
*/
continue;
case 'l':
flags |= FLLONG;
continue;
/*
* Actual conversion specifications go
* here.
*/
case 'c':
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR))
#endif /* SCANF_LEVEL > SCANF_MIN */
a.cp = va_arg(ap, char *);
#if SCANF_LEVEL > SCANF_MIN
if (width == SCHAR_MAX)
width = 1;
while (width-- > 0) {
#endif /* SCANF_LEVEL > SCANF_MIN */
if ((i = getc(stream)) == EOF)
goto leave;
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR))
#endif /* SCANF_LEVEL > SCANF_MIN */
*a.cp++ = i;
#if SCANF_LEVEL > SCANF_MIN
}
#endif /* SCANF_LEVEL > SCANF_MIN */
break;
case 's':
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR))
#endif /* SCANF_LEVEL > SCANF_MIN */
a.cp = va_arg(ap, char *);
do {
i = getc(stream);
} while (isspace(i));
if (i == EOF)
goto leave;
#if SCANF_LEVEL > SCANF_MIN
while (width-- > 0)
#else
for (;;)
#endif /* SCANF_LEVEL > SCANF_MIN */
{
if (isspace(i)) {
ungetc(i, stream);
break;
}
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR))
#endif /* SCANF_LEVEL > SCANF_MIN */
*a.cp++ = i;
if ((i = getc(stream)) == EOF)
break;
}
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR))
#endif /* SCANF_LEVEL > SCANF_MIN */
*a.cp = '\0';
break;
case 'o':
base = 8;
flags |= FLUNSIGNED;
goto dointeger;
case 'p':
/*
* Handle pointers as plain unsigned
* integers. This assumes that
* sizeof(void *) == sizeof(unsigned int).
*/
case 'x':
base = 16;
/* FALLTHROUGH */
case 'u':
flags |= FLUNSIGNED;
/* FALLTHROUGH */
case 'd':
case 'i':
dointeger:
do {
i = getc(stream);
} while (isspace(i));
if (i == EOF)
goto leave;
if ((char)i == '-' || (char)i == '+') {
#if SCANF_LEVEL > SCANF_MIN
if (--width <= 0)
/*
* Incomplete conversion
* due to field width
* truncation.
*/
goto leave;
#endif /* SCANF_LEVEL > SCANF_MIN */
if ((char)i == '-')
flags |= FLMINUS;
if ((i = getc(stream)) == EOF)
goto leave;
}
if ((char)i == '0') {
/*
* %i conversions default to base
* 10, but allow for base 8
* indicated by a leading 0 in
* input, or base 16 indicated by
* leading 0x/0X.
*
* For %x (and %p) conversions, the
* leading 0x/0X is explicitly
* allowable.
*
* If we fail the conversion here,
* it is a mismatch condition, but
* since we already saw a zero,
* this means the current
* conversion succeeded, assigning
* 0.
*/
a.ul = 0;
#if SCANF_LEVEL > SCANF_MIN
if (--width <= 0)
goto intdone;
#endif /* SCANF_LEVEL > SCANF_MIN */
if ((i = getc(stream)) == EOF)
goto intdone;
if ((char)tolower(i) == 'x') {
if (c == 'o' ||
c == 'd' || c == 'u') {
/*
* Invalid 0x in
* %d/%u/%o
*/
ungetc(i, stream);
goto intdone;
}
base = 16;
if ((i = getc(stream)) == EOF)
goto intdone;
} else if (c == 'i')
base = 8;
}
a.ul = 0;
for (;;) {
j = tolower(i);
/*
* First, assume it is a decimal
* digit.
*/
j -= '0';
if (j > 9) {
/*
* Not a decimal digit.
* Try hex next.
*/
j += '0'; /* undo "- '0'"
* above */
j -= 'a'; /* 'a' is first
* hex digit */
if (j >= 0)
/* 'a' has value
* 10 */
j += 10;
/*
* else: not a hex digit,
* gets caught below.
*/
}
if (j < 0 || j >= base) {
ungetc(i, stream);
break;
}
a.ul *= base;
a.ul += j;
#if SCANF_LEVEL > SCANF_MIN
if (--width <= 0)
break;
#endif /* SCANF_LEVEL > SCANF_MIN */
if ((i = getc(stream)) == EOF)
break;
}
/*
* This is a bit of a hack: while we
* collect all integer digits in an
* unsigned long number in order to be
* safe for unsigned conversions, the
* standard allows for optional signs.
* We are thus faced to the concept of
* possibly negating an unsigned
* number. :-/ We rely here on union a
* mapping the signed and unsigned
* fields suitably.
*/
if (flags & FLMINUS)
a.l = -a.l;
intdone:
#if SCANF_LEVEL > SCANF_MIN
if (!(flags & FLSTAR)) {
#endif /* SCANF_LEVEL > SCANF_MIN */
if ((flags & (FLLONG | FLUNSIGNED))
== (FLLONG | FLUNSIGNED))
*(va_arg(ap, unsigned long *)) =
a.ul;
else if (flags & (FLUNSIGNED))
*(va_arg(ap, unsigned *)) =
(unsigned)a.ul;
else if (flags & FLLONG)
*(va_arg(ap, long *)) = a.l;
else
*(va_arg(ap, int *)) =
(int)a.l;
#if SCANF_LEVEL > SCANF_MIN
}
#endif /* SCANF_LEVEL > SCANF_MIN */
break;
#if SCANF_LEVEL > SCANF_MIN
case 'n':
if (!(flags & FLSTAR))
*(va_arg(ap, int *)) = stream->len;
break;
#endif /* SCANF_LEVEL > SCANF_MIN */
#if SCANF_LEVEL >= SCANF_FLT
case 'e':
case 'f':
case 'g':
do {
i = getc(stream);
} while (isspace(i));
if (i == EOF)
goto leave;
if ((char)i == '-' || (char)i == '+') {
if ((char)i == '-')
flags |= FLMINUS;
if ((i = getc(stream)) == EOF)
goto leave;
}
a.d = 0.0;
for (bp = buf;
bp < buf + FLTBUF - 1 && width > 0;
width--) {
if (strchr(fltchars, i) == 0) {
ungetc(i, stream);
break;
}
if ((char)i == 'e' ||
(char)i == 'E') {
/*
* Prevent another 'E'
* from being recognized.
*/
fltchars[10] = 0;
*bp++ = i;
if ((i = getc(stream)) == EOF)
break;
if ((char)i != '-' &&
(char)i != '+')
continue;
} else if ((char)i == '.')
/*
* Prevent another dot from
* being recognized. If we
* already saw an 'E'
* above, we could not get
* here at all.
*/
fltchars[12] = 0;
*bp++ = i;
if ((i = getc(stream)) == EOF)
break;
}
*bp++ = 0;
a.d = strtod(buf, 0);
if (flags & FLMINUS)
a.d = -a.d;
*(va_arg(ap, double *)) = a.d;
/*
* Restore the 'E' and '.' chars that
* might have been clobbered above.
*/
fltchars[10] = 'E';
fltchars[12] = '.';
break;
case '[':
flags |= FLBRACKET;
i = j = 0;
memset(buf, 0, 256 / 8);
continue;
#endif /* SCANF_LEVEL >= SCANF_FLT */
}
#if SCANF_LEVEL >= SCANF_FLT
nextconv:
#endif
if (stream->len > olen) {
#if SCANF_LEVEL >= SCANF_STD
if (!(flags & FLSTAR))
#endif
nconvs++;
rv = 0;
} else if (c != 'n' || i == EOF)
/*
* If one conversion failed completely,
* punt.
*/
goto leave;
flags = 0;
} else if (c == '%') {
flags = FLHASPERCENT;
base = 10;
olen = stream->len;
#if SCANF_LEVEL > SCANF_MIN
width = SCHAR_MAX;
} else if (isspace(c)) {
/* match against any whitspace */
do {
i = getc(stream);
} while (isspace(i));
if (i == EOF)
goto leave;
ungetc(i, stream);
} else {
/* literal character in format, match it */
literal:
if ((i = getc(stream)) == EOF)
goto leave;
if (i != c)
goto leave;
#endif /* SCANF_LEVEL > SCANF_MIN */
}
}
leave:
/*
* If a conversion was aborted (usually due to input failure
* or end-of-file), adjust the total number of conversions
* done if at least one char could be read from the stream.
*/
if ((flags & FLHASPERCENT) && stream->len > olen) {
#if SCANF_LEVEL >= SCANF_STD
if (!(flags & FLSTAR))
#endif
nconvs++;
rv = 0;
}
/*
* If an error occurs before the first successful conversion,
* we ought to return EOF. Before getting here, all
* conversions maintain the last character read from the
* stream (or EOF) within variable `i'.
*/
if (i == EOF && nconvs == 0)
return rv;
return nconvs;
}