/***********************************************************************
 *                                                                      *
 *               This software is part of the ast package               *
 *          Copyright (c) 1985-2012 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 <wchar.h>

#include "ast_float.h"
#include "sfhdr.h"  // IWYU pragma: keep
#include "sfio.h"

/*      Dealing with $ argument addressing stuffs.
**
**      Written by Kiem-Phong Vo.
*/

static_fn char *sffmtint(const char *str, int *v) {
    for (*v = 0; isdigit(*str); ++str) *v = *v * 10 + (*str - '0');
    *v -= 1;
    return (char *)str;
}

static_fn Fmtpos_t *sffmtpos(Sfio_t *f, const char *form, va_list args, Sffmt_t *ft, int type) {
    int base, fmt, flags, dot, width, precis;
    ssize_t n_str, size;
    char *t_str;
    int v, n, skip, dollar, decimal, thousand;
    Sffmt_t savft;
    Fmtpos_t *fp; /* position array of arguments        */
    int argp, argn, maxp, need[FP_INDEX];
    SFMBDCL(fmbs)

    if (type < 0) {
        fp = NULL;
    } else if (!(fp = sffmtpos(f, form, args, ft, -1))) {
        return NULL;
    }

    dollar = decimal = thousand = 0;
    argn = maxp = -1;
    SFMBCLR(&fmbs);
    while ((n = *form)) {
        if (n != '%') /* collect the non-pattern chars */
        {
            for (;;) {
                form += SFMBLEN(form, &fmbs);
                if (*form == 0 || *form == '%') break;
            }
            continue;
        } else {
            form += 1;
        }
        if (*form == 0) {
            break;
        } else if (*form == '%') {
            form += 1;
            continue;
        }

        if (*form == '*' && type > 0) /* skip in scanning */
        {
            skip = 1;
            form += 1;
            argp = -1;
        } else /* get the position of this argument */
        {
            skip = 0;
            char *sp = sffmtint(form, &argp);
            if (*sp == '$') {
                dollar = 1;
                form = sp + 1;
            } else {
                argp = -1;
            }
        }

        flags = dot = 0;
        t_str = NULL;
        n_str = 0;
        size = width = precis = base = -1;
        for (n = 0; n < FP_INDEX; ++n) need[n] = -1;

    loop_flags: /* LOOP FOR \0, %, FLAGS, WIDTH, PRECISION, BASE, TYPE */
        fmt = *form++;
        switch (fmt) {
            case LEFTP: /* get the type enclosed in balanced parens */
                t_str = (char *)form;
                for (v = 1;;) {
                    switch (*form++) {
                        case 0: /* not balancable, 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;
                            n_str = form - t_str;
                            if (*t_str == '*') {
                                t_str = sffmtint(t_str + 1, &n);
                                if (*t_str == '$') {
                                    dollar = 1;
                                } else {
                                    n = -1;
                                }
                                if ((n = FP_SET(n, argn)) > maxp) maxp = n;
                                if (fp && fp[n].ft.fmt == 0) {
                                    fp[n].ft.fmt = LEFTP;
                                    fp[n].ft.form = (char *)form;
                                }
                                need[FP_STR] = n;
                            }
                            goto loop_flags;
                    }
                }

            case '-':
                flags |= SFFMT_LEFT;
                flags &= ~SFFMT_ZERO;
                goto loop_flags;
            case '0':
                if (!(flags & SFFMT_LEFT)) flags |= SFFMT_ZERO;
                goto loop_flags;
            case ' ':
                if (!(flags & SFFMT_SIGN)) flags |= SFFMT_BLANK;
                goto loop_flags;
            case '+':
                flags |= SFFMT_SIGN;
                flags &= ~SFFMT_BLANK;
                goto loop_flags;
            case '#':
                flags |= SFFMT_ALTER;
                goto loop_flags;
            case QUOTE:
                SFSETLOCALE(&decimal, &thousand)
                if (thousand > 0) flags |= SFFMT_THOUSAND;
                goto loop_flags;

            case '.':
                if ((dot += 1) == 2) base = 0; /* for %s,%c */
                if (isdigit(*form)) {
                    fmt = *form++;
                    goto dot_size;
                } else if (*form != '*') {
                    goto loop_flags;
                } else {
                    form += 1;
                }

            // FALLTHRU
            case '*':
                form = sffmtint(form, &n);
                if (*form == '$') {
                    dollar = 1;
                    form += 1;
                } else {
                    n = -1;
                }
                if ((n = FP_SET(n, argn)) > maxp) maxp = n;
                if (fp && fp[n].ft.fmt == 0) {
                    fp[n].ft.fmt = '.';
                    fp[n].ft.size = dot;
                    fp[n].ft.form = (char *)form;
                }
                if (dot <= 2) need[dot] = n;
                goto loop_flags;

            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            dot_size:
                for (v = fmt - '0', fmt = *form; isdigit(fmt); fmt = *++form) {
                    v = v * 10 + (fmt - '0');
                }
                if (dot == 0) {
                    width = v;
                } else if (dot == 1) {
                    precis = v;
                } else if (dot == 2) {
                    base = v;
                }
                goto loop_flags;
            case 'I': /* object length */
                size = -1;
                flags = (flags & ~SFFMT_TYPES) | SFFMT_IFLAG;
                if (isdigit(*form)) {
                    for (size = 0, n = *form; isdigit(n); n = *++form) size = size * 10 + (n - '0');
                } else if (*form == '*') {
                    form = sffmtint(form + 1, &n);
                    if (*form == '$') {
                        dollar = 1;
                        form += 1;
                    } else {
                        n = -1;
                    }
                    if ((n = FP_SET(n, argn)) > maxp) maxp = n;
                    if (fp && fp[n].ft.fmt == 0) {
                        fp[n].ft.fmt = 'I';
                        fp[n].ft.size = sizeof(int);
                        fp[n].ft.form = (char *)form;
                    }
                    need[FP_SIZE] = n;
                }
                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;
            default: { break; }  // should this be an abort()?
        }

        /* 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);
                }
            }
        }

        if (skip) continue;

        if ((argp = FP_SET(argp, argn)) > maxp) maxp = argp;

        if (dollar && fmt == '!') return NULL;

        if (fp && fp[argp].ft.fmt == 0) {
            fp[argp].ft.form = (char *)form;
            fp[argp].ft.fmt = fp[argp].fmt = fmt;
            fp[argp].ft.size = size;
            fp[argp].ft.flags = flags;
            fp[argp].ft.width = width;
            fp[argp].ft.precis = precis;
            fp[argp].ft.base = base;
            fp[argp].ft.t_str = t_str;
            fp[argp].ft.n_str = n_str;
            for (n = 0; n < FP_INDEX; ++n) fp[argp].need[n] = need[n];
        }
    }

    if (!fp) /* constructing position array only */
    {
        if (!dollar || !(fp = malloc((maxp + 1) * sizeof(Fmtpos_t)))) return NULL;
        for (n = 0; n <= maxp; ++n) fp[n].ft.fmt = 0;
        return fp;
    }

    /* get value for positions */
    if (ft) memcpy(&savft, ft, sizeof(*ft));
    for (n = 0; n <= maxp; ++n) {
        if (fp[n].ft.fmt == 0) /* gap: pretend it's a 'd' pattern */
        {
            fp[n].ft.fmt = 'd';
            fp[n].ft.width = 0;
            fp[n].ft.precis = 0;
            fp[n].ft.base = 0;
            fp[n].ft.size = 0;
            fp[n].ft.t_str = 0;
            fp[n].ft.n_str = 0;
            fp[n].ft.flags = 0;
            for (v = 0; v < FP_INDEX; ++v) fp[n].need[v] = -1;
        }

        if (ft && ft->extf) {
            fp[n].ft.version = ft->version;
            fp[n].ft.extf = ft->extf;
            fp[n].ft.eventf = ft->eventf;
            if ((v = fp[n].need[FP_WIDTH]) >= 0 && v < n) fp[n].ft.width = fp[v].argv.i;
            if ((v = fp[n].need[FP_PRECIS]) >= 0 && v < n) fp[n].ft.precis = fp[v].argv.i;
            if ((v = fp[n].need[FP_BASE]) >= 0 && v < n) fp[n].ft.base = fp[v].argv.i;
            if ((v = fp[n].need[FP_STR]) >= 0 && v < n) fp[n].ft.t_str = fp[v].argv.s;
            if ((v = fp[n].need[FP_SIZE]) >= 0 && v < n) fp[n].ft.size = fp[v].argv.i;

            memcpy(ft, &fp[n].ft, sizeof(Sffmt_t));
            va_copy(ft->args, args);
            ft->flags |= SFFMT_ARGPOS;
            v = (*ft->extf)(f, (void *)(&fp[n].argv), ft);
            va_copy(args, ft->args);
            memcpy(&fp[n].ft, ft, sizeof(Sffmt_t));
            if (v < 0) {
                memcpy(ft, &savft, sizeof(Sffmt_t));
                ft = NULL;
            }

            if (!(fp[n].ft.flags & SFFMT_VALUE)) {
                goto arg_list;
            } else if (_Sftype[fp[n].ft.fmt] & (SFFMT_INT | SFFMT_UINT)) {
                if (fp[n].ft.size == sizeof(short)) {
                    if (_Sftype[fp[n].ft.fmt] & SFFMT_INT) {
                        fp[n].argv.i = fp[n].argv.h;
                    } else {
                        fp[n].argv.i = fp[n].argv.uh;
                    }
                } else if (fp[n].ft.size == sizeof(char)) {
                    if (_Sftype[fp[n].ft.fmt] & SFFMT_INT) {
                        fp[n].argv.i = fp[n].argv.c;
                    } else {
                        fp[n].argv.i = fp[n].argv.uc;
                    }
                }
            } else if (_Sftype[fp[n].ft.fmt] & SFFMT_FLOAT) {
                if (fp[n].ft.size == sizeof(float)) fp[n].argv.d = fp[n].argv.f;
            }
        } else {
        arg_list:
            if (fp[n].ft.fmt == LEFTP) {
                fp[n].argv.s = va_arg(args, char *);
                fp[n].ft.size = strlen(fp[n].argv.s);
            } else if (fp[n].ft.fmt == '.' || fp[n].ft.fmt == 'I') {
                fp[n].argv.i = va_arg(args, int);
            } else if (fp[n].ft.fmt == '!') {
                if (ft) memcpy(ft, &savft, sizeof(Sffmt_t));
                fp[n].argv.ft = ft = va_arg(args, Sffmt_t *);
                if (ft->form) ft = NULL;
                if (ft) memcpy(&savft, ft, sizeof(Sffmt_t));
            } else if (type > 0) { /* from sfvscanf */
                fp[n].argv.vp = va_arg(args, void *);
            } else {
                switch (_Sftype[fp[n].ft.fmt]) {
                    case SFFMT_INT:
                    case SFFMT_UINT:
#if !_ast_intmax_long
                        if (size == sizeof(Sflong_t))
                            fp[n].argv.ll = va_arg(args, Sflong_t);
                        else
#endif
                            if (size == sizeof(long)) {
                            fp[n].argv.l = va_arg(args, long);
                        } else {
                            fp[n].argv.i = va_arg(args, int);
                        }
                        break;
                    case SFFMT_FLOAT:
#if !_ast_fltmax_double
                        if (size == sizeof(Sfdouble_t)) {
                            fp[n].argv.ld = va_arg(args, Sfdouble_t);
                        } else {
#endif
                            fp[n].argv.d = va_arg(args, double);
                        }
                        break;
                    case SFFMT_POINTER:
                        fp[n].argv.vp = va_arg(args, void *);
                        break;
                    case SFFMT_CHAR:
                        if (fp[n].ft.base >= 0) {
                            fp[n].argv.s = va_arg(args, char *);
                        } else if ((fp[n].ft.flags & SFFMT_LONG) || fp[n].ft.fmt == 'C') {
#if _wchar_t_is_int
                            fp[n].argv.wc = va_arg(args, wchar_t);
#else   // _wchar_t_is_int
                        fp[n].argv.wc = (wchar_t)va_arg(args, uint);
#endif  // _wchar_t_is_int
                        }
                        /* observe promotion rule */
                        else {
                            fp[n].argv.i = va_arg(args, int);
                        }
                        break;
                    default: /* unknown pattern */
                        break;
                }
            }
        }
    }

    if (ft) memcpy(ft, &savft, sizeof(Sffmt_t));
    return fp;
}

static const unsigned char flt_nan[] = {_ast_flt_nan_init};
static const unsigned char flt_inf[] = {_ast_flt_inf_init};
static const unsigned char dbl_nan[] = {_ast_dbl_nan_init};
static const unsigned char dbl_inf[] = {_ast_dbl_inf_init};
#ifdef _ast_ldbl_nan_init
static const unsigned char ldbl_nan[] = {_ast_ldbl_nan_init};
static const unsigned char ldbl_inf[] = {_ast_ldbl_inf_init};
#endif

/* function to initialize conversion tables */
static_fn int sfcvinit() {
    int d, l;

    for (d = 0; d <= SF_MAXCHAR; ++d) {
        _Sfcv36[d] = SF_RADIX;
        _Sfcv64[d] = SF_RADIX;
    }

    /* [0-9] */
    for (d = 0; d < 10; ++d) {
        _Sfcv36[(uchar)_Sfdigits[d]] = d;
        _Sfcv64[(uchar)_Sfdigits[d]] = d;
    }

    /* [a-z] */
    for (; d < 36; ++d) {
        _Sfcv36[(uchar)_Sfdigits[d]] = d;
        _Sfcv64[(uchar)_Sfdigits[d]] = d;
    }

    /* [A-Z] */
    for (l = 10; d < 62; ++l, ++d) {
        _Sfcv36[(uchar)_Sfdigits[d]] = l;
        _Sfcv64[(uchar)_Sfdigits[d]] = d;
    }

    /* remaining digits */
    for (; d < SF_RADIX; ++d) {
        _Sfcv36[(uchar)_Sfdigits[d]] = d;
        _Sfcv64[(uchar)_Sfdigits[d]] = d;
    }

    _Sftype['d'] = _Sftype['i'] = SFFMT_INT;
    _Sftype['u'] = _Sftype['o'] = _Sftype['x'] = _Sftype['X'] = SFFMT_UINT;
    _Sftype['e'] = _Sftype['E'] = _Sftype['a'] = _Sftype['A'] = _Sftype['g'] = _Sftype['G'] =
        _Sftype['f'] = SFFMT_FLOAT;
    _Sftype['s'] = _Sftype['n'] = _Sftype['p'] = _Sftype['!'] = SFFMT_POINTER;
    _Sftype['c'] = SFFMT_CHAR;
    _Sftype['['] = SFFMT_CLASS;
    _Sftype['S'] = SFFMT_POINTER;
    _Sftype['C'] = SFFMT_CHAR;

    /* IEEE floating point computed constants */

    memcpy((char *)&_Sffnan, (char *)flt_nan, sizeof(_Sffnan));
    memcpy((char *)&_Sffinf, (char *)flt_inf, sizeof(_Sffinf));
    memcpy((char *)&_Sfdnan, (char *)dbl_nan, sizeof(_Sfdnan));
    memcpy((char *)&_Sfdinf, (char *)dbl_inf, sizeof(_Sfdinf));
#ifdef _ast_ldbl_nan_init
    memcpy((char *)&_Sflnan, (char *)ldbl_nan, sizeof(_Sflnan));
    memcpy((char *)&_Sflinf, (char *)ldbl_inf, sizeof(_Sflinf));
#else
    memcpy((char *)&_Sflnan, (char *)dbl_nan, sizeof(_Sfdnan));
    memcpy((char *)&_Sflinf, (char *)dbl_inf, sizeof(_Sfdinf));
#endif

    return 1;
}

/* table for floating point and integer conversions */
#include "features/sfinit.h"  // IWYU pragma: keep
