HelenOS sources
This source file includes following definitions.
- digit_value
- cvtspec_parse
- strbuf_init
- strbuf_write
- __fgetc
- __ungetc
- vfscanf_skip_ws
- vfscanf_match_ws
- __fstrtoimax
- __fstrtoumax
- __fstrtold
- __fgetchars
- __fgetstr
- is_in_scanset
- __fgetscanstr
- vfscanf_cvt
- vfscanf
- fscanf
- vscanf
- scanf
#include <assert.h>
#include <_bits/ssize_t.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "../private/scanf.h"
typedef enum {
lm_none,
lm_hh,
lm_h,
lm_l,
lm_ll,
lm_j,
lm_z,
lm_t,
lm_L
} lenmod_t;
typedef enum {
cs_unknown,
cs_decimal,
cs_int,
cs_octal,
cs_udecimal,
cs_hex,
cs_float,
cs_char,
cs_str,
cs_set,
cs_ptr,
cs_numchar,
cs_percent
} cvtspcr_t;
typedef struct {
bool noassign;
bool memalloc;
bool have_width;
size_t width;
lenmod_t lenmod;
cvtspcr_t spcr;
const char *scanset;
} cvtspec_t;
typedef struct {
char *buf;
char **pptr;
bool memalloc;
size_t size;
} strbuf_t;
typedef struct {
va_list ap;
} va_encaps_t;
static int digit_value(char digit)
{
switch (digit) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
assert(false);
}
}
static void cvtspec_parse(const char **fmt, cvtspec_t *spec)
{
spec->scanset = NULL;
spec->width = 0;
if (**fmt == '*') {
spec->noassign = true;
++(*fmt);
} else {
spec->noassign = false;
}
if (**fmt == 'm') {
spec->memalloc = true;
++(*fmt);
} else {
spec->memalloc = false;
}
if (isdigit(**fmt)) {
spec->have_width = true;
assert(**fmt != '0');
spec->width = 0;
while (isdigit(**fmt)) {
spec->width *= 10;
spec->width += digit_value(**fmt);
++(*fmt);
}
} else {
spec->have_width = false;
}
switch (**fmt) {
case 'h':
++(*fmt);
if (**fmt == 'h') {
spec->lenmod = lm_hh;
++(*fmt);
} else {
spec->lenmod = lm_h;
}
break;
case 'l':
++(*fmt);
if (**fmt == 'l') {
spec->lenmod = lm_ll;
++(*fmt);
} else {
spec->lenmod = lm_l;
}
break;
case 'j':
++(*fmt);
spec->lenmod = lm_j;
break;
case 'z':
++(*fmt);
spec->lenmod = lm_z;
break;
case 't':
++(*fmt);
spec->lenmod = lm_t;
break;
case 'L':
++(*fmt);
spec->lenmod = lm_L;
break;
default:
spec->lenmod = lm_none;
break;
}
switch (**fmt) {
case 'd':
++(*fmt);
spec->spcr = cs_decimal;
break;
case 'i':
++(*fmt);
spec->spcr = cs_int;
break;
case 'o':
++(*fmt);
spec->spcr = cs_octal;
break;
case 'u':
++(*fmt);
spec->spcr = cs_udecimal;
break;
case 'x':
case 'X':
++(*fmt);
spec->spcr = cs_hex;
break;
case 'a':
case 'e':
case 'f':
case 'g':
case 'A':
case 'E':
case 'F':
case 'G':
++(*fmt);
spec->spcr = cs_float;
break;
case 'c':
++(*fmt);
spec->spcr = cs_char;
break;
case 's':
++(*fmt);
spec->spcr = cs_str;
break;
case '[':
++(*fmt);
spec->spcr = cs_set;
spec->scanset = *fmt;
while (**fmt != ']' && **fmt != '\0')
++(*fmt);
if (**fmt == ']')
++(*fmt);
break;
case 'p':
++(*fmt);
spec->spcr = cs_ptr;
break;
case 'n':
++(*fmt);
spec->spcr = cs_numchar;
break;
case '%':
++(*fmt);
spec->spcr = cs_percent;
break;
default:
assert(false);
spec->spcr = cs_unknown;
break;
}
}
static errno_t strbuf_init(strbuf_t *strbuf, cvtspec_t *spec, va_encaps_t *va)
{
if (spec->noassign) {
strbuf->memalloc = false;
strbuf->buf = NULL;
strbuf->pptr = NULL;
strbuf->size = 0;
return EOK;
}
if (spec->memalloc) {
strbuf->memalloc = true;
strbuf->size = 1;
strbuf->buf = malloc(strbuf->size);
if (strbuf->buf == NULL)
return ENOMEM;
strbuf->pptr = va_arg(va->ap, char **);
*strbuf->pptr = strbuf->buf;
} else {
strbuf->memalloc = false;
strbuf->size = 0;
strbuf->buf = va_arg(va->ap, char *);
strbuf->pptr = NULL;
}
return EOK;
}
static errno_t strbuf_write(strbuf_t *strbuf, size_t idx, char c)
{
if (strbuf->memalloc && idx >= strbuf->size) {
strbuf->size = strbuf->size * 2;
strbuf->buf = realloc(strbuf->buf, strbuf->size);
*strbuf->pptr = strbuf->buf;
if (strbuf->buf == NULL)
return ENOMEM;
}
if (strbuf->buf != NULL)
strbuf->buf[idx] = c;
return EOK;
}
static int __fgetc(FILE *f, int *numchar)
{
int c;
c = fgetc(f);
if (c == EOF)
return EOF;
++(*numchar);
return c;
}
static int __ungetc(int c, FILE *f, int *numchar)
{
int rc;
rc = ungetc(c, f);
if (rc == EOF)
return EOF;
--(*numchar);
return rc;
}
static errno_t vfscanf_skip_ws(FILE *f, int *numchar)
{
int c;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
while (isspace(c)) {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
}
if (c == EOF)
return EIO;
__ungetc(c, f, numchar);
return EOK;
}
static errno_t vfscanf_match_ws(FILE *f, int *numchar, const char **fmt)
{
errno_t rc;
rc = vfscanf_skip_ws(f, numchar);
if (rc == EOF)
return EIO;
++(*fmt);
return EOK;
}
static errno_t __fstrtoimax(FILE *f, int *numchar, int base, size_t width,
intmax_t *dest)
{
errno_t rc;
int c;
intmax_t v;
int digit;
int sign;
rc = vfscanf_skip_ws(f, numchar);
if (rc == EIO)
return EIO;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (c == '+' || c == '-') {
sign = (c == '-') ? -1 : +1;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
} else {
sign = 1;
}
if (!isdigit(c) || width < 1) {
__ungetc(c, f, numchar);
return EINVAL;
}
if (base == 0) {
if (c == '0') {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
if (width > 0 && (c == 'x' || c == 'X')) {
--width;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (width > 0 && isxdigit(c)) {
base = 16;
} else {
*dest = 0;
return EOK;
}
} else {
base = 8;
if (width == 0) {
*dest = 0;
return EOK;
}
}
} else {
base = 10;
}
}
v = 0;
do {
digit = digit_value(c);
if (digit >= base)
break;
v = v * base + digit;
c = __fgetc(f, numchar);
--width;
} while (width > 0 && isxdigit(c));
if (c != EOF)
__ungetc(c, f, numchar);
*dest = sign * v;
return EOK;
}
static errno_t __fstrtoumax(FILE *f, int *numchar, int base, size_t width,
uintmax_t *dest)
{
errno_t rc;
int c;
uintmax_t v;
int digit;
rc = vfscanf_skip_ws(f, numchar);
if (rc == EIO)
return EIO;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (!isdigit(c) || width < 1) {
__ungetc(c, f, numchar);
return EINVAL;
}
if (base == 0) {
if (c == '0') {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
if (width > 0 && (c == 'x' || c == 'X')) {
--width;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (width > 0 && isxdigit(c)) {
base = 16;
} else {
*dest = 0;
return EOK;
}
} else {
base = 8;
if (width == 0) {
*dest = 0;
return EOK;
}
}
} else {
base = 10;
}
}
v = 0;
do {
digit = digit_value(c);
if (digit >= base)
break;
v = v * base + digit;
c = __fgetc(f, numchar);
--width;
} while (width > 0 && isxdigit(c));
if (c != EOF)
__ungetc(c, f, numchar);
*dest = v;
return EOK;
}
errno_t __fstrtold(FILE *f, int *numchar, size_t width,
long double *dest)
{
errno_t rc;
int c;
long double v;
int digit;
int sign;
int base;
int efactor;
int eadd;
int eadj;
int exp;
int expsign;
rc = vfscanf_skip_ws(f, numchar);
if (rc == EIO)
return EIO;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (c == '+' || c == '-') {
sign = (c == '-') ? -1 : +1;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
} else {
sign = 1;
}
if (!isdigit(c) || width < 1) {
__ungetc(c, f, numchar);
return EINVAL;
}
base = 10;
efactor = 10;
eadd = 1;
if (c == '0') {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
if (width > 0 && (c == 'x' || c == 'X')) {
--width;
c = __fgetc(f, numchar);
if (width > 0 && isxdigit(c)) {
base = 16;
efactor = 2;
eadd = 4;
} else {
*dest = 0;
return EOK;
}
}
}
v = 0;
do {
digit = digit_value(c);
if (digit >= base)
break;
v = v * base + digit;
c = __fgetc(f, numchar);
--width;
} while (width > 0 && isxdigit(c));
eadj = 0;
if (c == '.' && width > 1) {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
while (width > 0 && isxdigit(c)) {
digit = digit_value(c);
if (digit >= base)
break;
v = v * base + digit;
c = __fgetc(f, numchar);
--width;
eadj -= eadd;
}
}
exp = 0;
if ((width > 1 && base == 10 && (c == 'e' || c == 'E')) ||
(width > 1 && base == 16 && (c == 'p' || c == 'P'))) {
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
if (width > 1 && (c == '+' || c == '-')) {
if (c == '+') {
expsign = 1;
} else {
expsign = -1;
}
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
--width;
} else {
expsign = 1;
}
while (width > 0 && isdigit(c)) {
digit = digit_value(c);
if (digit >= 10)
break;
exp = exp * 10 + digit;
c = __fgetc(f, numchar);
--width;
}
exp = exp * expsign;
}
exp += eadj;
while (exp > 0) {
v = v * efactor;
--exp;
}
while (exp < 0) {
v = v / efactor;
++exp;
}
if (c != EOF)
__ungetc(c, f, numchar);
*dest = sign * v;
return EOK;
}
static errno_t __fgetchars(FILE *f, int *numchar, size_t width,
strbuf_t *strbuf, size_t *nread)
{
size_t cnt;
int c;
errno_t rc;
*nread = 0;
for (cnt = 0; cnt < width; cnt++) {
c = __fgetc(f, numchar);
if (c == EOF) {
*nread = cnt;
return EIO;
}
rc = strbuf_write(strbuf, cnt, c);
if (rc != EOK) {
*nread = cnt;
return rc;
}
}
*nread = cnt;
return EOK;
}
static errno_t __fgetstr(FILE *f, int *numchar, size_t width, strbuf_t *strbuf,
size_t *nread)
{
size_t cnt;
int c;
errno_t rc;
errno_t rc2;
*nread = 0;
rc = vfscanf_skip_ws(f, numchar);
if (rc == EIO)
return EIO;
rc = EOK;
for (cnt = 0; cnt < width; cnt++) {
c = __fgetc(f, numchar);
if (c == EOF) {
rc = EIO;
break;
}
if (isspace(c)) {
__ungetc(c, f, numchar);
break;
}
rc = strbuf_write(strbuf, cnt, c);
if (rc != EOK) {
*nread = cnt;
return rc;
}
}
rc2 = strbuf_write(strbuf, cnt, '\0');
if (rc2 != EOK) {
*nread = cnt;
return rc2;
}
*nread = cnt;
return rc;
}
static bool is_in_scanset(char c, const char *scanset)
{
const char *p = scanset;
bool inverted = false;
char startc;
char endc;
if (*p == '^') {
inverted = true;
++p;
}
if (*p == ']') {
if (c == ']')
return !inverted;
++p;
} else if (*p == '-') {
if (c == '-')
return !inverted;
++p;
}
while (*p != '\0' && *p != ']') {
if (*p == '-' && p[1] != ']' && p[1] != '\0') {
startc = p[-1];
endc = p[1];
if (c >= startc && c <= endc)
return !inverted;
p += 2;
continue;
}
if (*p == c)
return !inverted;
++p;
}
return inverted;
}
static errno_t __fgetscanstr(FILE *f, int *numchar, size_t width,
const char *scanset, strbuf_t *strbuf, size_t *nread)
{
size_t cnt;
int c;
errno_t rc;
errno_t rc2;
rc = EOK;
for (cnt = 0; cnt < width; cnt++) {
c = __fgetc(f, numchar);
if (c == EOF) {
rc = EIO;
break;
}
if (!is_in_scanset(c, scanset)) {
__ungetc(c, f, numchar);
break;
}
rc = strbuf_write(strbuf, cnt, c);
if (rc != EOK) {
*nread = cnt;
break;
}
}
rc2 = strbuf_write(strbuf, cnt, '\0');
if (rc2 != EOK) {
*nread = cnt;
return rc2;
}
*nread = cnt;
return rc;
}
static errno_t vfscanf_cvt(FILE *f, const char **fmt, va_encaps_t *va,
int *numchar, unsigned *ncvt)
{
errno_t rc;
int c;
intmax_t ival;
uintmax_t uval;
long double fval = 0.0;
int *iptr;
short *sptr;
signed char *scptr;
long *lptr;
long long *llptr;
ssize_t *ssptr;
ptrdiff_t *pdptr;
intmax_t *imptr;
unsigned *uptr;
unsigned short *usptr;
unsigned char *ucptr;
unsigned long *ulptr;
unsigned long long *ullptr;
float *fptr;
double *dptr;
long double *ldptr;
size_t *szptr;
ptrdiff_t *updptr;
uintmax_t *umptr;
void **pptr;
size_t width;
cvtspec_t cvtspec;
strbuf_t strbuf;
size_t nread;
++(*fmt);
cvtspec_parse(fmt, &cvtspec);
width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
switch (cvtspec.spcr) {
case cs_percent:
rc = vfscanf_skip_ws(f, numchar);
if (rc == EOF)
return EIO;
c = __fgetc(f, numchar);
if (c == EOF)
return EIO;
if (c != '%') {
__ungetc(c, f, numchar);
return EINVAL;
}
break;
case cs_decimal:
rc = __fstrtoimax(f, numchar, 10, width, &ival);
if (rc != EOK)
return rc;
break;
case cs_udecimal:
rc = __fstrtoumax(f, numchar, 10, width, &uval);
if (rc != EOK)
return rc;
break;
case cs_octal:
rc = __fstrtoumax(f, numchar, 8, width, &uval);
if (rc != EOK)
return rc;
break;
case cs_hex:
rc = __fstrtoumax(f, numchar, 16, width, &uval);
if (rc != EOK)
return rc;
break;
case cs_float:
rc = __fstrtold(f, numchar, width, &fval);
if (rc != EOK)
return rc;
break;
case cs_int:
rc = __fstrtoimax(f, numchar, 0, width, &ival);
if (rc != EOK)
return rc;
break;
case cs_ptr:
rc = __fstrtoumax(f, numchar, 0, width, &uval);
if (rc != EOK)
return rc;
break;
case cs_char:
rc = strbuf_init(&strbuf, &cvtspec, va);
if (rc != EOK)
return rc;
width = cvtspec.have_width ? cvtspec.width : 1;
rc = __fgetchars(f, numchar, width, &strbuf, &nread);
if (rc != EOK) {
if (rc == ENOMEM)
return EIO;
assert(rc == EIO);
if (nread > 0)
++(*ncvt);
return EIO;
}
break;
case cs_str:
rc = strbuf_init(&strbuf, &cvtspec, va);
if (rc != EOK)
return rc;
width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
rc = __fgetstr(f, numchar, width, &strbuf, &nread);
if (rc != EOK) {
if (rc == ENOMEM)
return EIO;
assert(rc == EIO);
if (nread > 0)
++(*ncvt);
return EIO;
}
break;
case cs_set:
rc = strbuf_init(&strbuf, &cvtspec, va);
if (rc != EOK)
return rc;
width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
rc = __fgetscanstr(f, numchar, width, cvtspec.scanset,
&strbuf, &nread);
if (rc != EOK) {
if (rc == ENOMEM)
return EIO;
assert(rc == EIO);
if (nread > 0)
++(*ncvt);
return EIO;
}
break;
case cs_numchar:
break;
default:
return EINVAL;
}
if (cvtspec.noassign)
goto skip_assign;
switch (cvtspec.spcr) {
case cs_percent:
break;
case cs_decimal:
case cs_int:
switch (cvtspec.lenmod) {
case lm_none:
iptr = va_arg(va->ap, int *);
*iptr = ival;
break;
case lm_hh:
scptr = va_arg(va->ap, signed char *);
*scptr = ival;
break;
case lm_h:
sptr = va_arg(va->ap, short *);
*sptr = ival;
break;
case lm_l:
lptr = va_arg(va->ap, long *);
*lptr = ival;
break;
case lm_ll:
llptr = va_arg(va->ap, long long *);
*llptr = ival;
break;
case lm_j:
imptr = va_arg(va->ap, intmax_t *);
*imptr = ival;
break;
case lm_z:
ssptr = va_arg(va->ap, ssize_t *);
*ssptr = ival;
break;
case lm_t:
pdptr = va_arg(va->ap, ptrdiff_t *);
*pdptr = ival;
break;
default:
assert(false);
}
++(*ncvt);
break;
case cs_udecimal:
case cs_octal:
case cs_hex:
switch (cvtspec.lenmod) {
case lm_none:
uptr = va_arg(va->ap, unsigned *);
*uptr = uval;
break;
case lm_hh:
ucptr = va_arg(va->ap, unsigned char *);
*ucptr = uval;
break;
case lm_h:
usptr = va_arg(va->ap, unsigned short *);
*usptr = uval;
break;
case lm_l:
ulptr = va_arg(va->ap, unsigned long *);
*ulptr = uval;
break;
case lm_ll:
ullptr = va_arg(va->ap, unsigned long long *);
*ullptr = uval;
break;
case lm_j:
umptr = va_arg(va->ap, uintmax_t *);
*umptr = uval;
break;
case lm_z:
szptr = va_arg(va->ap, size_t *);
*szptr = uval;
break;
case lm_t:
updptr = va_arg(va->ap, ptrdiff_t *);
*updptr = uval;
break;
default:
assert(false);
}
++(*ncvt);
break;
case cs_float:
switch (cvtspec.lenmod) {
case lm_none:
fptr = va_arg(va->ap, float *);
*fptr = fval;
break;
case lm_l:
dptr = va_arg(va->ap, double *);
*dptr = fval;
break;
case lm_L:
ldptr = va_arg(va->ap, long double *);
*ldptr = fval;
break;
default:
assert(false);
}
++(*ncvt);
break;
case cs_ptr:
pptr = va_arg(va->ap, void *);
*pptr = (void *)(uintptr_t)uval;
++(*ncvt);
break;
case cs_char:
++(*ncvt);
break;
case cs_str:
++(*ncvt);
break;
case cs_set:
++(*ncvt);
break;
case cs_numchar:
iptr = va_arg(va->ap, int *);
*iptr = *numchar;
break;
default:
return EINVAL;
}
skip_assign:
return EOK;
}
int vfscanf(FILE *f, const char *fmt, va_list ap)
{
const char *cp;
int c;
unsigned ncvt;
int numchar;
bool input_error = false;
va_encaps_t va;
int rc;
va_copy(va.ap, ap);
ncvt = 0;
numchar = 0;
cp = fmt;
while (*cp != '\0') {
if (isspace(*cp)) {
rc = vfscanf_match_ws(f, &numchar, &cp);
if (rc == EIO) {
input_error = true;
break;
}
assert(rc == EOK);
} else if (*cp == '%') {
rc = vfscanf_cvt(f, &cp, &va, &numchar,
&ncvt);
if (rc == EIO) {
input_error = true;
break;
}
if (rc != EOK)
break;
} else {
c = __fgetc(f, &numchar);
if (c == EOF) {
input_error = true;
break;
}
if (c != *cp) {
__ungetc(c, f, &numchar);
break;
}
++cp;
}
}
if (input_error && ncvt == 0)
return EOF;
return ncvt;
}
int fscanf(FILE *f, const char *fmt, ...)
{
va_list args;
int rc;
va_start(args, fmt);
rc = vfscanf(f, fmt, args);
va_end(args);
return rc;
}
int vscanf(const char *fmt, va_list ap)
{
return vfscanf(stdin, fmt, ap);
}
int scanf(const char *fmt, ...)
{
va_list args;
int rc;
va_start(args, fmt);
rc = vscanf(fmt, args);
va_end(args);
return rc;
}
HelenOS homepage, sources at GitHub