| /* |
| * 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; |
| } |
| |
| static inline void |
| set_bit(unsigned long *bitmap, unsigned int bit) |
| { |
| bitmap[bit/LONG_BIT] |= 1UL << (bit%LONG_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 ) { |
| converted++; |
| } else { |
| bail = *q ? bail_err : bail_eof; |
| } |
| break; |
| } |
| } |
| |
| if ( bail == bail_eof && !converted ) |
| converted = -1; /* Return EOF (-1) */ |
| |
| return converted; |
| } |