| /* |
| * 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> |
| #include <sys/bitops.h> |
| |
| #ifndef LONG_BIT |
| #define LONG_BIT (CHAR_BIT*sizeof(long)) |
| #endif |
| |
| enum flags { |
| FL_SPLAT = 0x01, /* Drop the value, do not assign */ |
| FL_WIDTH = 0x02, /* Field width specified */ |
| FL_MINUS = 0x04, /* 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 */ |
| }; |
| |
| 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 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; |
| goto scan_int; |
| |
| case 'i': /* Base-independent integer */ |
| base = 0; |
| goto scan_int; |
| |
| case 'd': /* Decimal integer */ |
| base = 10; |
| goto scan_int; |
| |
| case 'o': /* Octal integer */ |
| base = 8; |
| goto scan_int; |
| |
| case 'u': /* Unsigned decimal integer */ |
| base = 10; |
| goto scan_int; |
| |
| case 'x': /* Hexadecimal integer */ |
| case 'X': |
| base = 16; |
| 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 == '^' && !matchinv) { |
| matchinv = 1; |
| } else { |
| range_start = (unsigned char)ch; |
| set_bit((unsigned char)ch, matchmap); |
| state = st_match; |
| } |
| break; |
| |
| case st_match: /* Main state for %[ match */ |
| if (ch == ']') { |
| goto match_run; |
| } else if (ch == '-') { |
| state = st_match_range; |
| } else { |
| range_start = (unsigned char)ch; |
| set_bit((unsigned char)ch, matchmap); |
| } |
| break; |
| |
| case st_match_range: /* %[ match after - */ |
| if (ch == ']') { |
| set_bit((unsigned char)'-', matchmap); /* - was last character */ |
| goto match_run; |
| } else { |
| int i; |
| for (i = range_start; i <= (unsigned char)ch; i++) |
| set_bit(i, matchmap); |
| state = st_match; |
| } |
| break; |
| |
| match_run: /* Match expression finished */ |
| qq = q; |
| while (width && *q |
| && test_bit((unsigned char)*q, matchmap) ^ 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; |
| } |