/***********************************************************************
 *                                                                      *
 *               This software is part of the ast package               *
 *          Copyright (c) 1985-2013 AT&T Intellectual Property          *
 *                      and is licensed under the                       *
 *                 Eclipse Public License, Version 1.0                  *
 *                    by AT&T Intellectual Property                     *
 *                                                                      *
 *                A copy of the License is available at                 *
 *          http://www.eclipse.org/org/documents/epl-v10.html           *
 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
 *                                                                      *
 *              Information and Software Systems Research               *
 *                            AT&T Research                             *
 *                           Florham Park NJ                            *
 *                                                                      *
 *               Glenn Fowler <glenn.s.fowler@gmail.com>                *
 *                    David Korn <dgkorn@gmail.com>                     *
 *                     Phong Vo <phongvo@gmail.com>                     *
 *                                                                      *
 ***********************************************************************/
#include "config_ast.h"  // IWYU pragma: keep

#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <wchar.h>

#include "sfhdr.h"
#include "sfio.h"

/*      The main engine for reading formatted data
**
**      Written by Kiem-Phong Vo.
*/

#define MAXWIDTH (int)(((uint)~0) >> 1) /* max amount to scan   */

/*
 * pull in a private strtold()
 */

#include "sfstrtof.h"

/* refresh stream buffer - taking care of unseekable/share streams too */
static_fn void _sfbuf(Sfio_t *f, int *peek) {
    if (f->next >= f->endb) {
        if (*peek) /* try peeking for a share stream if possible */
        {
            f->mode |= SF_RV;
            if (SFFILBUF(f, -1) > 0) {
                f->mode |= SF_PEEK;
                return;
            }
            *peek = 0; /* can't peek, back to normal reads */
        }
        (void)SFFILBUF(f, -1);
    }
}

/* buffer used during scanning of a double value or a multi-byte
   character. the fields mirror certain local variables in sfvscanf.  */
typedef struct _scan_s {
    int error;              /* get set by _sfdscan if no value specified        */
    int inp;                /* last input character read                        */
    int width;              /* field width                                      */
    Sfio_t *f;              /* stream being scanned                             */
    uchar *d, *endd, *data; /* local buffering system   */
    int peek;               /* != 0 if unseekable/share stream          */
    int n_input;            /* number of input bytes processed          */
} Scan_t;

// _width = `-1` to scan non-double values; else `width` to scan double values.
#define SCinit(sc, _width)       \
    do {                         \
        (sc)->inp = -1;          \
        (sc)->error = -1;        \
        (sc)->f = f;             \
        (sc)->width = _width;    \
        (sc)->d = d;             \
        (sc)->endd = endd;       \
        (sc)->data = data;       \
        (sc)->peek = peek;       \
        (sc)->n_input = n_input; \
    } while (0)
#define SCinit_single(sc) SCinit((sc), -1)
#define SCinit_double(sc) SCinit((sc), width)

// _width = `width` if scanning non-double values; else `sc->width` if scanning double values.
#define SCend(sc, _width)        \
    do {                         \
        inp = (sc)->inp;         \
        f = (sc)->f;             \
        width = _width;          \
        d = (sc)->d;             \
        endd = (sc)->endd;       \
        data = (sc)->data;       \
        peek = (sc)->peek;       \
        n_input = (sc)->n_input; \
    } while (0)
#define SCend_single(sc) SCend((sc), width)
#define SCend_double(sc) SCend((sc), (sc)->width)

static_fn int _scgetc(void *arg, int flag) {
    Scan_t *sc = (Scan_t *)arg;

    if (flag) {
        sc->error = flag;
        return 0;
    }

    /* if width >= 0, do not allow to exceed width number of bytes */
    if (sc->width == 0) {
        sc->inp = -1;
        return 0;
    }

    if (sc->d >= sc->endd) /* refresh local buffer */
    {
        sc->n_input += sc->d - sc->data;
        if (sc->peek) {
            SFREAD(sc->f, sc->data, sc->d - sc->data);
        } else {
            sc->f->next = sc->d;
        }

        _sfbuf(sc->f, &sc->peek);
        sc->data = sc->d = sc->f->next;
        sc->endd = sc->f->endb;

        if (sc->d >= sc->endd) {
            sc->inp = -1;
            return 0;
        }
    }

    sc->width -= 1;
    if (sc->width >= 0) {  // from _sfdscan
        sc->inp = (int)(*sc->d++);
        return sc->inp;
    }
    return (int)(*sc->d++);
}

/* structure to match characters in a character class */
typedef struct _accept_s {
    char ok[SF_MAXCHAR + 1];
    int yes;
    char *form, *endf;
    wchar_t wc;
} Accept_t;

static_fn char *_sfsetclass(const char *form, Accept_t *ac, int flags) {
    int c, endc, n;
    SFMBDCL(mbs)

    if (*form == '^') /* complementing this set */
    {
        ac->yes = 0;
        form += 1;
    } else {
        ac->yes = 1;
    }

    for (c = 0; c <= SF_MAXCHAR; ++c) ac->ok[c] = !ac->yes;

    if (*form == ']' || *form == '-') /* special first char */
    {
        ac->ok[*form] = ac->yes;
        form += 1;
    }
    ac->form = (char *)form;

    if (flags & SFFMT_LONG) SFMBCLR(&mbs);
    for (n = 1; *form != ']'; form += n) {
        if ((c = *((uchar *)form)) == 0) return NULL;

        if (*(form + 1) == '-') {
            endc = *((uchar *)(form + 2));
            if (c >= 128 || endc >= 128) { /* range must be ascii */
                goto one_char;
            }
            for (; c <= endc; ++c) ac->ok[c] = ac->yes;
            n = 3;
        } else {
        one_char:
            if ((flags & SFFMT_LONG) && (n = (int)SFMBLEN(form, &mbs)) <= 0) return NULL;
            if (n == 1) ac->ok[c] = ac->yes;
        }
    }

    ac->endf = (char *)form;
    return (char *)(form + 1);
}

static_fn int _sfwaccept(wchar_t wc, Accept_t *ac) {
    int endc, c, n;
    wchar_t fwc;
    char *form = ac->form;
    SFMBDCL(mbs)

    SFMBCLR(&mbs);
    for (; *form != ']'; form += n) {
        if ((c = *((uchar *)form)) == 0) return 0;

        if (*(form + 1) == '-') {
            endc = *((uchar *)(form + 2));
            if (c >= 128 || endc >= 128) {  // range must be ascii and thus single byte char
                n = mbrtowc(&fwc, form, ac->endf - form, (mbstate_t *)&mbs);
                if (n > 1 && wc == fwc) return ac->yes;
            }
            n = 3;
        } else {
            n = mbrtowc(&fwc, form, ac->endf - form, (mbstate_t *)&mbs);
            if (n > 1 && wc == fwc) return ac->yes;
        }
    }

    return !ac->yes;
}

static_fn int _sfgetwc(Scan_t *sc, wchar_t *wc, int fmt, Accept_t *ac, void *mbs) {
    int n, v;
    char b[16]; /* assuming that MB_CUR_MAX <= 16! */

#if 0
    // TODO: Figure out why the author of this code thought this was legal.
    // Specifically, the memcpy() which modifies the buffer. This causes a SIGSEGV if the buffer is
    // in a read-only memory page. This was found via one of the sfsscanf() tests in
    // src/cmd/tests/sfio/twchar.c.

    /* shift left data so that there will be more room to back up on error.
       this won't help streams with small buffers - c'est la vie! */
    if (sc->d > sc->f->data && (n = sc->endd - sc->d) > 0 && n < MB_CUR_MAX) {
        memcpy(sc->f->data, sc->d, n);
        if (sc->f->endr == sc->f->endb) sc->f->endr = sc->f->data + n;
        if (sc->f->endw == sc->f->endb) sc->f->endw = sc->f->data + n;
        sc->f->endb = sc->f->data + n;
        sc->d = sc->data = sc->f->data;
        sc->endd = sc->f->endb;
        if (!mbs) sc->f->endb = sc->endd; /* stop cc's "unused mbs" warning */
    }
#endif

    for (n = 0; n < MB_CUR_MAX;) {
        if ((v = _scgetc((void *)sc, 0)) <= 0) {
            goto no_match;
        } else {
            b[n++] = v;
        }

        size_t rv = mbrtowc(wc, b, n, mbs);
        if (rv == (size_t)(-1)) goto no_match;  // malformed multi-byte char
        if (rv == (size_t)(-2)) continue;       // incomplete multi-byte char

        // Multi-byte char converted successfully.
        if (fmt == 'c') {
            return 1;
        } else if (fmt == 's') {
            if (n > 1 || (n == 1 && !isspace(b[0]))) return 1;
        } else if (fmt == '[') {
            if (n == 1 && ac->ok[b[0]]) return 1;
            if (n > 1 && _sfwaccept(*wc, ac)) return 1;
            goto no_match;
        } else {  // if(fmt == '1') match a single wchar_t
            if (*wc == ac->wc) return 1;
            goto no_match;
        }
    }

no_match:  // this unget is lossy on a stream with small buffer
    if ((sc->d -= n) < sc->data) sc->d = sc->data;
    return 0;
}

int sfvscanf(Sfio_t *f, const char *form, va_list args) {
    int inp, shift, base, width;
    ssize_t size;
    int fmt, flags, dot, n_assign, v, n, n_input;
    char *sp;

    Accept_t acc;

    Argv_t argv;
    Sffmt_t *ft;
    Fmt_t *fm, *fmstk;

    Fmtpos_t *fp;
    char *oform;
    va_list oargs;
    int argp, argn;

    int decimal = 0, thousand = 0;

    wchar_t wc;
    SFMBDCL(fmbs)
    SFMBDCL(mbs)

    void *value; /* location to assign scanned value */
    char *t_str;
    ssize_t n_str;

    /* local buffering system */
    Scan_t scd;
    uchar *d, *endd, *data;
    int peek;
#define SFbuf(f) (_sfbuf(f, &peek), (data = d = f->next), (endd = f->endb))
#define SFlen(f) (d - data)
#define SFinit(f) ((peek = f->extent < 0 && (f->flags & SF_SHARE)), SFbuf(f))
#define SFend(f) \
    ((n_input += SFlen(f)), (peek ? SFREAD(f, (void *)data, SFlen(f)) : ((f->next = d), 0)))
#define SFgetc(f, c) ((c) = (d < endd || (SFend(f), SFbuf(f), d < endd)) ? (int)(*d++) : -1)
#define SFungetc(f, c) (d -= 1)

    SFMTXDECL(f)

    SFCVINIT()  // initialize conversion tables

    SFMTXENTER(f, -1)

    if (!form || (f->mode != SF_READ && _sfmode(f, SF_READ, 0) < 0)) SFMTXRETURN(f, -1)
    SFLOCK(f, 0)

    SFinit(f); /* initialize local buffering system */

    n_assign = n_input = 0;
    inp = -1;

    fmstk = NULL;
    ft = NULL;

    fp = NULL;
    argn = -1;
    oform = (char *)form;
    va_copy(oargs, args);

    SFSETLOCALE(&decimal, &thousand)

loop_fmt:
    SFMBCLR(&fmbs);
    while ((fmt = *form++)) {
        if (fmt != '%') {
            if (isspace(fmt)) {
                if (fmt != '\n' || !(f->flags & SF_LINE)) fmt = -1;
                for (;;) {
                    if (SFgetc(f, inp) < 0 || inp == fmt) {
                        goto loop_fmt;
                    } else if (!isspace(inp)) {
                        SFungetc(f, inp);
                        goto loop_fmt;
                    }
                }
            } else {
            match_1:
                if ((n = (int)mbrtowc(&wc, form - 1, MB_CUR_MAX, (mbstate_t *)&fmbs)) <= 0) {
                    goto pop_fmt;
                }
                if (n > 1) {
                    acc.wc = wc;
                    SCinit_single(&scd);
                    SFMBCLR(&mbs);
                    v = _sfgetwc(&scd, &wc, '1', &acc, &mbs);
                    SCend_single(&scd);
                    if (v == 0) goto pop_fmt;
                    form += n - 1;
                } else if (SFgetc(f, inp) != fmt) {
                    if (inp < 0) goto done;
                    SFungetc(f, inp);
                    goto pop_fmt;
                }
            }
            continue;
        }

        if (*form == '%') {
            form += 1;
            do {
                SFgetc(f, inp);
            } while (isspace(inp)); /* skip starting blanks */
            SFungetc(f, inp);
            goto match_1;
        }

        if (*form == '\0') goto pop_fmt;

        if (*form == '*') {
            flags = SFFMT_SKIP;
            form += 1;
        } else {
            flags = 0;
        }

        /* matching some pattern */
        base = 10;
        size = -1;
        width = dot = 0;
        t_str = NULL;
        n_str = 0;
        value = NULL;
        argp = -1;

    loop_flags: /* LOOP FOR FLAGS, WIDTH, BASE, TYPE */
        switch ((fmt = *form++)) {
            case LEFTP: /* get the type which is enclosed in balanced () */
                t_str = (char *)form;
                for (v = 1;;) {
                    switch (*form++) {
                        case 0: /* not balanceable, retract */
                            form = t_str;
                            t_str = NULL;
                            n_str = 0;
                            goto loop_flags;
                        case LEFTP: /* increasing nested level */
                            v += 1;
                            continue;
                        case RIGHTP: /* decreasing nested level */
                            if ((v -= 1) != 0) continue;
                            if (*t_str != '*') {
                                n_str = (form - 1) - t_str;
                            } else {
                                t_str = (*_Sffmtintf)(t_str + 1, &n);
                                if (*t_str == '$') {
                                    if (!fp && !(fp = (*_Sffmtposf)(f, oform, oargs, ft, 1))) {
                                        goto pop_fmt;
                                    }
                                    n = FP_SET(n, argn);
                                } else {
                                    n = FP_INC(argn);
                                }

                                if (fp) {
                                    t_str = fp[n].argv.s;
                                    n_str = fp[n].ft.size;
                                } else if (ft && ft->extf) {
                                    FMTSET(ft, form, args, LEFTP, 0, 0, 0, 0, 0, NULL, 0);
                                    n = (*ft->extf)(f, (void *)&argv, ft);
                                    if (n < 0) goto pop_fmt;
                                    if (!(ft->flags & SFFMT_VALUE)) goto t_arg;
                                    if ((t_str = argv.s) && (n_str = (int)ft->size) < 0) {
                                        n_str = strlen(t_str);
                                    }
                                } else {
                                t_arg:
                                    if ((t_str = va_arg(args, char *))) n_str = strlen(t_str);
                                }
                            }
                            goto loop_flags;
                    }
                }

            case '#': /* alternative format */
                flags |= SFFMT_ALTER;
                goto loop_flags;

            case '.': /* width & base */
                dot += 1;
                if (isdigit(*form)) {
                    fmt = *form++;
                    goto dot_size;
                } else if (*form == '*') {
                    form = (*_Sffmtintf)(form + 1, &n);
                    if (*form == '$') {
                        form += 1;
                        if (!fp && !(fp = (*_Sffmtposf)(f, oform, oargs, ft, 1))) goto pop_fmt;
                        n = FP_SET(n, argn);
                    } else {
                        n = FP_INC(argn);
                    }

                    if (fp) {
                        v = fp[n].argv.i;
                    } else if (ft && ft->extf) {
                        FMTSET(ft, form, args, '.', dot, 0, 0, 0, 0, NULL, 0);
                        if ((*ft->extf)(f, (void *)(&argv), ft) < 0) goto pop_fmt;
                        if (ft->flags & SFFMT_VALUE) {
                            v = argv.i;
                        } else {
                            v = (dot <= 2) ? va_arg(args, int) : 0;
                        }
                    } else {
                        v = (dot <= 2) ? va_arg(args, int) : 0;
                    }
                    if (v < 0) v = 0;
                    goto dot_set;
                } else {
                    goto loop_flags;
                }

            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            dot_size:
                for (v = fmt - '0'; isdigit(*form); ++form) v = v * 10 + (*form - '0');

                if (*form == '$') {
                    form += 1;
                    if (!fp && !(fp = (*_Sffmtposf)(f, oform, oargs, ft, 1))) goto pop_fmt;
                    argp = v - 1;
                    goto loop_flags;
                }

            dot_set:
                if (dot == 0 || dot == 1) {
                    width = v;
                } else if (dot == 2) {
                    base = v;
                }
                goto loop_flags;

            case 'z': /* ssize_t or object size */
            case 'I': /* object size */
                size = -1;
                flags = (flags & ~SFFMT_TYPES) | SFFMT_IFLAG;
                if (*form == '*') {
                    form = (*_Sffmtintf)(form + 1, &n);
                    if (*form == '$') {
                        form += 1;
                        if (!fp && !(fp = (*_Sffmtposf)(f, oform, oargs, ft, 1))) goto pop_fmt;
                        n = FP_SET(n, argn);
                    } else {
                        n = FP_INC(argn);
                    }

                    if (fp) { /* use position list */
                        size = fp[n].argv.i;
                    } else if (ft && ft->extf) {
                        FMTSET(ft, form, args, 'I', sizeof(int), 0, 0, 0, 0, NULL, 0);
                        if ((*ft->extf)(f, (void *)(&argv), ft) < 0) goto pop_fmt;
                        if (ft->flags & SFFMT_VALUE) {
                            size = argv.i;
                        } else {
                            size = va_arg(args, int);
                        }
                    } else {
                        size = va_arg(args, int);
                    }
                } else if (fmt == 'z') {
                    flags = (flags & ~SFFMT_TYPES) | SFFMT_ZFLAG;
                } else if (isdigit(*form)) {
                    for (size = 0, n = *form; isdigit(n); n = *++form) size = size * 10 + (n - '0');
                }
                goto loop_flags;

            case 'l':
                size = -1;
                flags &= ~SFFMT_TYPES;
                if (*form == 'l') {
                    form += 1;
                    flags |= SFFMT_LLONG;
                } else {
                    flags |= SFFMT_LONG;
                }
                goto loop_flags;
            case 'h':
                size = -1;
                flags &= ~SFFMT_TYPES;
                if (*form == 'h') {
                    form += 1;
                    flags |= SFFMT_SSHORT;
                } else {
                    flags |= SFFMT_SHORT;
                }
                goto loop_flags;
            case 'L':
                size = -1;
                flags = (flags & ~SFFMT_TYPES) | SFFMT_LDOUBLE;
                goto loop_flags;
            case 'j':
                size = -1;
                flags = (flags & ~SFFMT_TYPES) | SFFMT_JFLAG;
                goto loop_flags;
            case 't':
                size = -1;
                flags = (flags & ~SFFMT_TYPES) | SFFMT_TFLAG;
                goto loop_flags;
            case QUOTE:
                if (thousand > 0) flags |= SFFMT_THOUSAND;
                goto loop_flags;
        }

        /* set object size for scalars */
        if (flags & SFFMT_TYPES) {
            if ((_Sftype[fmt] & (SFFMT_INT | SFFMT_UINT)) || fmt == 'n') {
                if (flags & SFFMT_LONG) {
                    size = sizeof(long);
                } else if (flags & SFFMT_SHORT) {
                    size = sizeof(short);
                } else if (flags & SFFMT_SSHORT) {
                    size = sizeof(char);
                } else if (flags & SFFMT_TFLAG) {
                    size = sizeof(ptrdiff_t);
                } else if (flags & SFFMT_ZFLAG) {
                    size = sizeof(size_t);
                } else if (flags & (SFFMT_LLONG | SFFMT_JFLAG)) {
                    size = sizeof(Sflong_t);
                } else if (flags & SFFMT_IFLAG) {
                    if (size <= 0 || size == sizeof(Sflong_t) * CHAR_BIT) size = sizeof(Sflong_t);
                } else if (size < 0) {
                    size = sizeof(int);
                }
            } else if (_Sftype[fmt] & SFFMT_FLOAT) {
                if (flags & (SFFMT_LONG | SFFMT_LLONG)) {
                    size = sizeof(double);
                } else if (flags & SFFMT_LDOUBLE) {
                    size = sizeof(Sfdouble_t);
                } else if (flags & SFFMT_IFLAG) {
                    if (size <= 0) size = sizeof(Sfdouble_t);
                } else if (size < 0) {
                    size = sizeof(float);
                }
            } else if (_Sftype[fmt] & SFFMT_CHAR) {
                if ((flags & SFFMT_LONG) || fmt == 'C') {
#if _ast_sizeof_wchar_t > _ast_sizeof_int
                    size = _ast_sizeof_wchar_t;
#else
                    size = _ast_sizeof_int;
#endif
                } else if (size < 0) {
                    size = sizeof(int);
                }
            }
        }

        argp = FP_SET(argp, argn);
        if (fp) {
            if (!(fp[argp].ft.flags & SFFMT_SKIP)) {
                n_assign += 1;
                value = fp[argp].argv.vp;
                size = fp[argp].ft.size;
                if (ft && ft->extf && fp[argp].ft.fmt != fp[argp].fmt) fmt = fp[argp].ft.fmt;
            } else {
                flags |= SFFMT_SKIP;
            }
        } else if (ft && ft->extf) {
            FMTSET(ft, form, args, fmt, size, flags, width, 0, base, t_str, n_str);
            SFend(f);
            SFOPEN(f)
            v = (*ft->extf)(f, (void *)&argv, ft);
            SFLOCK(f, 0)
            SFbuf(f);

            if (v < 0) {
                goto pop_fmt;
            } else if (v > 0) /* extf comsumed v input bytes */
            {
                n_input += v;
                if (!(ft->flags & SFFMT_SKIP)) n_assign += 1;
                continue;
            } else /* if(v == 0): extf did not use input stream */
            {
                FMTGET(ft, form, args, fmt, size, flags, width, n, base);

                if ((ft->flags & SFFMT_VALUE) && !(ft->flags & SFFMT_SKIP)) value = argv.vp;
            }
        }

        if (_Sftype[fmt] == 0) { /* unknown pattern */
            goto pop_fmt;
        }

        if (fmt == '!') {
            if (!fp) {
                fp = (*_Sffmtposf)(f, oform, oargs, ft, 1);
            } else {
                goto pop_fmt;
            }

            if (!(argv.ft = va_arg(args, Sffmt_t *))) continue;
            if (!argv.ft->form && ft) /* change extension functions */
            {
                if (ft->eventf && (*ft->eventf)(f, SF_DPOP, (void *)form, ft) < 0) continue;
                fmstk->ft = ft = argv.ft;
            } else /* stack a new environment */
            {
                fm = malloc(sizeof(Fmt_t));
                if (!fm) goto done;

                ft = fm->ft = argv.ft;
                SFMBSET(ft->mbs, &fmbs);
                if (ft->form) {
                    fm->form = (char *)form;
                    SFMBCPY(&fm->mbs, &fmbs);
                    va_copy(fm->args, args);

                    fm->oform = oform;
                    va_copy(fm->oargs, oargs);
                    fm->argn = argn;
                    fm->fp = fp;

                    form = ft->form;
                    SFMBCLR(ft->mbs);
                    va_copy(args, ft->args);
                    argn = -1;
                    fp = NULL;
                    oform = (char *)form;
                    va_copy(oargs, args);
                } else {
                    fm->form = NULL;
                }

                fm->eventf = ft->eventf;
                fm->next = fmstk;
                fmstk = fm;
            }
            continue;
        }

        /* get the address to assign value */
        if (!value && !(flags & SFFMT_SKIP)) value = va_arg(args, void *);

        if (fmt == 'n') /* return length of consumed input */
        {
#if !_ast_intmax_long
            if (size == sizeof(Sflong_t))
                *((Sflong_t *)value) = (Sflong_t)(n_input + SFlen(f));
            else
#endif
                if (size == sizeof(long)) {
                *((long *)value) = (long)(n_input + SFlen(f));
            } else if (size == sizeof(short)) {
                *((short *)value) = (short)(n_input + SFlen(f));
            } else if (size == sizeof(uchar)) {
                *((uchar *)value) = (uchar)(n_input + SFlen(f));
            } else {
                *((int *)value) = (int)(n_input + SFlen(f));
            }
            continue;
        }

        /* if get here, start scanning input */
        if (width == 0) width = fmt == 'c' ? 1 : MAXWIDTH;

        /* define the first input character */
        if (fmt == 'c' || fmt == '[' || fmt == 'C') {
            SFgetc(f, inp);
        } else {
            do {
                SFgetc(f, inp);
            } while (isspace(inp)); /* skip starting blanks */
        }
        if (inp < 0) goto done;

        if (_Sftype[fmt] == SFFMT_FLOAT) {
            SFungetc(f, inp);
            SCinit_double(&scd);
            argv.ld = _sfdscan((void *)(&scd), _scgetc);
            SCend_double(&scd);

            if (scd.error >= 0) {
                if (inp >= 0) SFungetc(f, inp);
                goto pop_fmt;
            }

            if (value) {
#if !_ast_fltmax_double
                if (size == sizeof(Sfdouble_t)) {
                    *((Sfdouble_t *)value) = argv.ld;
                } else
#endif
                    if (size == sizeof(double)) {
                    *((double *)value) = (double)argv.ld;
                } else {
                    *((float *)value) = (float)argv.ld;
                }

                n_assign += 1;
            }
        } else if (_Sftype[fmt] == SFFMT_UINT || fmt == 'p') {
            if (inp == '-') {
                SFungetc(f, inp);
                goto pop_fmt;
            } else {
                goto int_cvt;
            }
        } else if (_Sftype[fmt] == SFFMT_INT) {
        int_cvt:
            if (inp == '-' || inp == '+') {
                if (inp == '-') flags |= SFFMT_MINUS;
                while (--width > 0 && SFgetc(f, inp) >= 0) {
                    if (!isspace(inp)) break;
                }
            }
            if (inp < 0) goto done;

            if (fmt == 'o') {
                base = 8;
            } else if (fmt == 'x' || fmt == 'X' || fmt == 'p') {
                base = 16;
            } else if (fmt == 'i' && inp == '0') /* self-described data */
            {
                base = 8;
                if (width > 1) /* peek to see if it's a base-16 */
                {
                    if (SFgetc(f, inp) >= 0) {
                        if (inp == 'x' || inp == 'X') base = 16;
                        SFungetc(f, inp);
                    }
                    inp = '0';
                }
            }

            /* now convert */
            argv.lu = 0;
            if (base == 16) {
                sp = (char *)_Sfcv36;
                shift = 4;
                if (sp[inp] >= 16) {
                    SFungetc(f, inp);
                    goto pop_fmt;
                }
                if (inp == '0' && --width > 0) { /* skip leading 0x or 0X */
                    if (SFgetc(f, inp) >= 0 && (inp == 'x' || inp == 'X') && --width > 0) {
                        SFgetc(f, inp);
                    }
                }
                if (inp >= 0 && sp[inp] < 16) goto base_shift;
            } else if (base == 10) {
                for (n = v = 0;;) { /* fast base 10 conversion */
#define TEN(x) (((x) << 3) + ((x) << 1))
                    if (inp >= '0' && inp <= '9') {
                        argv.lu = TEN(argv.lu) + (inp - '0');
                        n += 1;
                    } else if (inp == thousand) {
                        if ((v && n != 3) || (!v && n > 3)) break;
                        v = 1;
                        n = 0;
                    } else {
                        break;
                    }
                    if ((width -= 1) <= 0 || SFgetc(f, inp) < 0) break;
                }
                if (!n && !v) {
                    SFungetc(f, inp);
                    goto pop_fmt;
                }

                if (fmt == 'i' && inp == '#' && !(flags & SFFMT_ALTER)) {
                    base = (int)argv.lu;
                    if (base < 2 || base > SF_RADIX) goto pop_fmt;
                    argv.lu = 0;
                    sp = (char *)(base <= 36 ? _Sfcv36 : _Sfcv64);
                    if (--width > 0 && SFgetc(f, inp) >= 0 && sp[inp] < base) goto base_conv;
                }
            } else { /* other bases */
                sp = (char *)(base <= 36 ? _Sfcv36 : _Sfcv64);
                if (base < 2 || base > SF_RADIX || sp[inp] >= base) {
                    SFungetc(f, inp);
                    goto pop_fmt;
                }

            base_conv: /* check for power of 2 conversions */
                if ((base & ~(base - 1)) == base) {
                    if (base < 8) {
                        shift = base < 4 ? 1 : 2;
                    } else if (base < 32) {
                        shift = base < 16 ? 3 : 4;
                    } else {
                        shift = base < 64 ? 5 : 6;
                    }

                base_shift:
                    do {
                        argv.lu = (argv.lu << shift) + sp[inp];
                    } while (--width > 0 && SFgetc(f, inp) >= 0 && sp[inp] < base);
                } else {
                    do {
                        argv.lu = (argv.lu * base) + sp[inp];
                    } while (--width > 0 && SFgetc(f, inp) >= 0 && sp[inp] < base);
                }
            }

            if (flags & SFFMT_MINUS) argv.ll = -argv.ll;

            if (value) {
                n_assign += 1;

                if (fmt == 'p') {
#if _more_void_int
                    *((void **)value) = (void *)((ulong)argv.lu);
#else
                    *((void **)value) = (void *)((uint)argv.lu);
#endif
#if !_ast_intmax_long
                } else if (size == sizeof(Sflong_t)) {
                    *((Sflong_t *)value) = argv.ll;
#endif
                } else if (size == sizeof(long)) {
                    if (fmt == 'd' || fmt == 'i') {
                        *((long *)value) = (long)argv.ll;
                    } else {
                        *((ulong *)value) = (ulong)argv.lu;
                    }
                } else if (size == sizeof(short)) {
                    if (fmt == 'd' || fmt == 'i') {
                        *((short *)value) = (short)argv.ll;
                    } else {
                        *((ushort *)value) = (ushort)argv.lu;
                    }
                } else if (size == sizeof(char)) {
                    if (fmt == 'd' || fmt == 'i') {
                        *((char *)value) = (char)argv.ll;
                    } else {
                        *((uchar *)value) = (uchar)argv.lu;
                    }
                } else {
                    if (fmt == 'd' || fmt == 'i') {
                        *((int *)value) = (int)argv.ll;
                    } else {
                        *((uint *)value) = (uint)argv.lu;
                    }
                }
            }
        } else if (fmt == 'C' || fmt == 'S') {
            fmt = fmt == 'C' ? 'c' : 's';
            flags = (flags & ~SFFMT_TYPES) | SFFMT_LONG;
            goto do_string;
        } else if (fmt == 's' || fmt == 'c' || fmt == '[') {
        do_string:
            if (value) {
                if (size < 0) size = MAXWIDTH;
                if (fmt != 'c') size -= 1;
                if (flags & SFFMT_LONG) {
                    argv.ws = (wchar_t *)value;
                } else {
                    argv.s = (char *)value;
                }
            } else {
                size = 0;
            }

            if (fmt == '[' && !(form = _sfsetclass(form, &acc, flags))) {
                SFungetc(f, inp);
                goto pop_fmt;
            }

            n = 0; /* count number of scanned characters */
            if (flags & SFFMT_LONG) {
                SFungetc(f, inp);
                SCinit_single(&scd);
                SFMBCLR(&mbs);
                for (; width > 0; --width) {
                    if (_sfgetwc(&scd, &wc, fmt, &acc, &mbs) == 0) break;
                    if ((n += 1) <= size) *argv.ws++ = wc;
                }
                SCend_single(&scd);
            } else if (fmt == 's') {
                do {
                    if (isspace(inp)) break;
                    if ((n += 1) <= size) *argv.s++ = inp;
                } while (--width > 0 && SFgetc(f, inp) >= 0);
            } else if (fmt == 'c') {
                do {
                    if ((n += 1) <= size) *argv.s++ = inp;
                } while (--width > 0 && SFgetc(f, inp) >= 0);
            } else /* if(fmt == '[') */
            {
                do {
                    if (!acc.ok[inp]) {
                        if (n > 0 || (flags & SFFMT_ALTER)) {
                            break;
                        } else {
                            SFungetc(f, inp);
                            goto pop_fmt;
                        }
                    }
                    if ((n += 1) <= size) *argv.s++ = inp;
                } while (--width > 0 && SFgetc(f, inp) >= 0);
            }

            if (value && (n > 0 || fmt == '[')) {
                n_assign += 1;
                if (fmt != 'c' && size >= 0) {
                    if (flags & SFFMT_LONG) {
                        *argv.ws = 0;
                    } else {
                        *argv.s = 0;
                    }
                }
            }
        }

        if (width > 0 && inp >= 0) SFungetc(f, inp);
    }

pop_fmt:
    if (fp) {
        free(fp);
        fp = NULL;
    }
    while ((fm = fmstk)) /* pop the format stack and continue */
    {
        if (fm->eventf) {
            if (!form || !form[0]) {
                (*fm->eventf)(f, SF_FINAL, NULL, ft);
            } else if ((*fm->eventf)(f, SF_DPOP, (void *)form, ft) < 0) {
                goto loop_fmt;
            }
        }

        fmstk = fm->next;
        if ((form = fm->form)) {
            SFMBCPY(&fmbs, &fm->mbs);
            va_copy(args, fm->args);
            oform = fm->oform;
            va_copy(oargs, fm->oargs);
            argn = fm->argn;
            fp = fm->fp;
        }
        ft = fm->ft;
        free(fm);
        if (form && form[0]) goto loop_fmt;
    }

done:
    if (fp) free(fp);
    while ((fm = fmstk)) {
        if (fm->eventf) (*fm->eventf)(f, SF_FINAL, NULL, fm->ft);
        fmstk = fm->next;
        free(fm);
    }

    SFend(f);
    SFOPEN(f)

    if (n_assign == 0 && inp < 0) n_assign = -1;

    va_end(oargs);
    SFMTXRETURN(f, n_assign)
}
