601 lines
14 KiB
C
Executable File
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;
|
|
}
|