366 lines
8.0 KiB
C
366 lines
8.0 KiB
C
/*
|
|
* vsscanf.c
|
|
*
|
|
* vsscanf(), from which the rest of the scanf()
|
|
* family is built
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
|
|
#ifndef LONG_BIT
|
|
#define LONG_BIT (CHAR_BIT*sizeof(long))
|
|
#endif
|
|
|
|
enum flags {
|
|
FL_SPLAT = 0x01, /* Drop the value, do not assign */
|
|
FL_INV = 0x02, /* Character-set with inverse */
|
|
FL_WIDTH = 0x04, /* Field width specified */
|
|
FL_MINUS = 0x08, /* Negative number */
|
|
};
|
|
|
|
enum ranks {
|
|
rank_char = -2,
|
|
rank_short = -1,
|
|
rank_int = 0,
|
|
rank_long = 1,
|
|
rank_longlong = 2,
|
|
rank_ptr = INT_MAX /* Special value used for pointers */
|
|
};
|
|
|
|
#define MIN_RANK rank_char
|
|
#define MAX_RANK rank_longlong
|
|
|
|
#define INTMAX_RANK rank_longlong
|
|
#define SIZE_T_RANK rank_long
|
|
#define PTRDIFF_T_RANK rank_long
|
|
|
|
enum bail {
|
|
bail_none = 0, /* No error condition */
|
|
bail_eof, /* Hit EOF */
|
|
bail_err /* Conversion mismatch */
|
|
};
|
|
|
|
static inline const char *
|
|
skipspace(const char *p)
|
|
{
|
|
while ( isspace((unsigned char)*p) ) p++;
|
|
return p;
|
|
}
|
|
|
|
#undef set_bit
|
|
static inline void
|
|
set_bit(unsigned long *bitmap, unsigned int bit)
|
|
{
|
|
bitmap[bit/LONG_BIT] |= 1UL << (bit%LONG_BIT);
|
|
}
|
|
|
|
#undef test_bit
|
|
static inline int
|
|
test_bit(unsigned long *bitmap, unsigned int bit)
|
|
{
|
|
return (int)(bitmap[bit/LONG_BIT] >> (bit%LONG_BIT)) & 1;
|
|
}
|
|
|
|
int vsscanf(const char *buffer, const char *format, va_list ap)
|
|
{
|
|
const char *p = format;
|
|
char ch;
|
|
const char *q = buffer;
|
|
const char *qq;
|
|
uintmax_t val = 0;
|
|
int rank = rank_int; /* Default rank */
|
|
unsigned int width = UINT_MAX;
|
|
int base;
|
|
enum flags flags = 0;
|
|
enum {
|
|
st_normal, /* Ground state */
|
|
st_flags, /* Special flags */
|
|
st_width, /* Field width */
|
|
st_modifiers, /* Length or conversion modifiers */
|
|
st_match_init, /* Initial state of %[ sequence */
|
|
st_match, /* Main state of %[ sequence */
|
|
st_match_range, /* After - in a %[ sequence */
|
|
} state = st_normal;
|
|
char *sarg = NULL; /* %s %c or %[ string argument */
|
|
enum bail bail = bail_none;
|
|
int sign;
|
|
int converted = 0; /* Successful conversions */
|
|
unsigned long matchmap[((1 << CHAR_BIT)+(LONG_BIT-1))/LONG_BIT];
|
|
int matchinv = 0; /* Is match map inverted? */
|
|
unsigned char range_start = 0;
|
|
|
|
while ( (ch = *p++) && !bail ) {
|
|
switch ( state ) {
|
|
case st_normal:
|
|
if ( ch == '%' ) {
|
|
state = st_flags;
|
|
flags = 0; rank = rank_int; width = UINT_MAX;
|
|
} else if ( isspace((unsigned char)ch) ) {
|
|
q = skipspace(q);
|
|
} else {
|
|
if ( *q == ch )
|
|
q++;
|
|
else
|
|
bail = bail_err; /* Match failure */
|
|
}
|
|
break;
|
|
|
|
case st_flags:
|
|
switch ( ch ) {
|
|
case '*':
|
|
flags |= FL_SPLAT;
|
|
break;
|
|
case '0' ... '9':
|
|
width = (ch-'0');
|
|
state = st_width;
|
|
flags |= FL_WIDTH;
|
|
break;
|
|
default:
|
|
state = st_modifiers;
|
|
p--; /* Process this character again */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case st_width:
|
|
if ( ch >= '0' && ch <= '9' ) {
|
|
width = width*10+(ch-'0');
|
|
} else {
|
|
state = st_modifiers;
|
|
p--; /* Process this character again */
|
|
}
|
|
break;
|
|
|
|
case st_modifiers:
|
|
switch ( ch ) {
|
|
/* Length modifiers - nonterminal sequences */
|
|
case 'h':
|
|
rank--; /* Shorter rank */
|
|
break;
|
|
case 'l':
|
|
rank++; /* Longer rank */
|
|
break;
|
|
case 'j':
|
|
rank = INTMAX_RANK;
|
|
break;
|
|
case 'z':
|
|
rank = SIZE_T_RANK;
|
|
break;
|
|
case 't':
|
|
rank = PTRDIFF_T_RANK;
|
|
break;
|
|
case 'L':
|
|
case 'q':
|
|
rank = rank_longlong; /* long double/long long */
|
|
break;
|
|
|
|
default:
|
|
/* Output modifiers - terminal sequences */
|
|
state = st_normal; /* Next state will be normal */
|
|
if ( rank < MIN_RANK ) /* Canonicalize rank */
|
|
rank = MIN_RANK;
|
|
else if ( rank > MAX_RANK )
|
|
rank = MAX_RANK;
|
|
|
|
switch ( ch ) {
|
|
case 'P': /* Upper case pointer */
|
|
case 'p': /* Pointer */
|
|
#if 0 /* Enable this to allow null pointers by name */
|
|
q = skipspace(q);
|
|
if ( !isdigit((unsigned char)*q) ) {
|
|
static const char * const nullnames[] =
|
|
{ "null", "nul", "nil", "(null)", "(nul)", "(nil)", 0 };
|
|
const char * const *np;
|
|
|
|
/* Check to see if it's a null pointer by name */
|
|
for ( np = nullnames ; *np ; np++ ) {
|
|
if ( !strncasecmp(q, *np, strlen(*np)) ) {
|
|
val = (uintmax_t)((void *)NULL);
|
|
goto set_integer;
|
|
}
|
|
}
|
|
/* Failure */
|
|
bail = bail_err;
|
|
break;
|
|
}
|
|
/* else */
|
|
#endif
|
|
rank = rank_ptr;
|
|
base = 0; sign = 0;
|
|
goto scan_int;
|
|
|
|
case 'i': /* Base-independent integer */
|
|
base = 0; sign = 1;
|
|
goto scan_int;
|
|
|
|
case 'd': /* Decimal integer */
|
|
base = 10; sign = 1;
|
|
goto scan_int;
|
|
|
|
case 'o': /* Octal integer */
|
|
base = 8; sign = 0;
|
|
goto scan_int;
|
|
|
|
case 'u': /* Unsigned decimal integer */
|
|
base = 10; sign = 0;
|
|
goto scan_int;
|
|
|
|
case 'x': /* Hexadecimal integer */
|
|
case 'X':
|
|
base = 16; sign = 0;
|
|
goto scan_int;
|
|
|
|
case 'n': /* Number of characters consumed */
|
|
val = (q-buffer);
|
|
goto set_integer;
|
|
|
|
scan_int:
|
|
q = skipspace(q);
|
|
if ( !*q ) {
|
|
bail = bail_eof;
|
|
break;
|
|
}
|
|
val = strntoumax(q, (char **)&qq, base, width);
|
|
if ( qq == q ) {
|
|
bail = bail_err;
|
|
break;
|
|
}
|
|
q = qq;
|
|
converted++;
|
|
/* fall through */
|
|
|
|
set_integer:
|
|
if ( !(flags & FL_SPLAT) ) {
|
|
switch(rank) {
|
|
case rank_char:
|
|
*va_arg(ap, unsigned char *) = (unsigned char)val;
|
|
break;
|
|
case rank_short:
|
|
*va_arg(ap, unsigned short *) = (unsigned short)val;
|
|
break;
|
|
case rank_int:
|
|
*va_arg(ap, unsigned int *) = (unsigned int)val;
|
|
break;
|
|
case rank_long:
|
|
*va_arg(ap, unsigned long *) = (unsigned long)val;
|
|
break;
|
|
case rank_longlong:
|
|
*va_arg(ap, unsigned long long *) = (unsigned long long)val;
|
|
break;
|
|
case rank_ptr:
|
|
*va_arg(ap, void **) = (void *)(uintptr_t)val;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'c': /* Character */
|
|
width = (flags & FL_WIDTH) ? width : 1; /* Default width == 1 */
|
|
sarg = va_arg(ap, char *);
|
|
while ( width-- ) {
|
|
if ( !*q ) {
|
|
bail = bail_eof;
|
|
break;
|
|
}
|
|
*sarg++ = *q++;
|
|
}
|
|
if ( !bail )
|
|
converted++;
|
|
break;
|
|
|
|
case 's': /* String */
|
|
{
|
|
char *sp;
|
|
sp = sarg = va_arg(ap, char *);
|
|
while ( width-- && *q && !isspace((unsigned char)*q) ) {
|
|
*sp++ = *q++;
|
|
}
|
|
if ( sarg != sp ) {
|
|
*sp = '\0'; /* Terminate output */
|
|
converted++;
|
|
} else {
|
|
bail = bail_eof;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '[': /* Character range */
|
|
sarg = va_arg(ap, char *);
|
|
state = st_match_init;
|
|
matchinv = 0;
|
|
memset(matchmap, 0, sizeof matchmap);
|
|
break;
|
|
|
|
case '%': /* %% sequence */
|
|
if ( *q == '%' )
|
|
q++;
|
|
else
|
|
bail = bail_err;
|
|
break;
|
|
|
|
default: /* Anything else */
|
|
bail = bail_err; /* Unknown sequence */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case st_match_init: /* Initial state for %[ match */
|
|
if ( ch == '^' && !(flags & FL_INV) ) {
|
|
matchinv = 1;
|
|
} else {
|
|
set_bit(matchmap, (unsigned char)ch);
|
|
state = st_match;
|
|
}
|
|
break;
|
|
|
|
case st_match: /* Main state for %[ match */
|
|
if ( ch == ']' ) {
|
|
goto match_run;
|
|
} else if ( ch == '-' ) {
|
|
range_start = (unsigned char)ch;
|
|
state = st_match_range;
|
|
} else {
|
|
set_bit(matchmap, (unsigned char)ch);
|
|
}
|
|
break;
|
|
|
|
case st_match_range: /* %[ match after - */
|
|
if ( ch == ']' ) {
|
|
set_bit(matchmap, (unsigned char)'-'); /* - was last character */
|
|
goto match_run;
|
|
} else {
|
|
int i;
|
|
for ( i = range_start ; i < (unsigned char)ch ; i++ )
|
|
set_bit(matchmap, i);
|
|
state = st_match;
|
|
}
|
|
break;
|
|
|
|
match_run: /* Match expression finished */
|
|
qq = q;
|
|
while ( width && *q && test_bit(matchmap, (unsigned char)*q)^matchinv ) {
|
|
*sarg++ = *q++;
|
|
}
|
|
if ( q != qq ) {
|
|
*sarg = '\0';
|
|
converted++;
|
|
} else {
|
|
bail = *q ? bail_err : bail_eof;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bail == bail_eof && !converted )
|
|
converted = -1; /* Return EOF (-1) */
|
|
|
|
return converted;
|
|
}
|