root/usr/src/lib/libxcurses/src/libc/stdio/vfscanf.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1996, by Sun Microsystems, Inc.
 * All rights reserved.
 */

/*
 * System V.2 Emulation Stdio Library -- vfscanf
 *
 * Copyright 1985, 1992 by Mortice Kern Systems Inc.  All rights reserved.
 *
 */

#ifdef M_RCSID
#ifndef lint
static char rcsID[] = "$Id: vfscanf.c 1.27 1995/09/20 19:07:52 ant Exp $";
#endif
#endif

#include <mks.h>
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#ifdef __FLOAT__
#include <math.h>
#endif

#define CONVTYPE        1
#define STAR            2
#define PERCENT         3
#define NUMBER          4
#define MODCONVL        5
#define NSCAN           6
#define BRACKET         7
#define MODCONVH        8

#define BASE16  16
#define BASE10  10
#define BASE8   8
#define NOBASE  0
#define SIGNED  1
#define UNSIGNED 0

#define CBUFSIZ 100     /* size of character buffer for input conversion */

struct lexlist {
        char    name;
        char    type;
};
static struct lexlist *lexp;
static struct lexlist lexlist[] ={
        '*',    STAR,
        '%',    PERCENT,
        'l',    MODCONVL,
        'h',    MODCONVH,
        'n',    NSCAN,
        '[',    BRACKET,
        'd',    CONVTYPE,
        'S',    CONVTYPE,       /* dummy entry (for multibyte characters) */
        's',    CONVTYPE,
        'u',    CONVTYPE,
        'c',    CONVTYPE,
        'x',    CONVTYPE,
        'o',    CONVTYPE,
        '0',    NUMBER,
        '1',    NUMBER,
        '2',    NUMBER,
        '3',    NUMBER,
        '4',    NUMBER,
        '5',    NUMBER,
        '6',    NUMBER,
        '7',    NUMBER,
        '8',    NUMBER,
        '9',    NUMBER,
        'i',    CONVTYPE,
        'f',    CONVTYPE,
        'e',    CONVTYPE,
        'g',    CONVTYPE,
        0,      0
};

static int      scan(int, const char *, const char *);
static  int     gettoken(void);
static  void    whitespace(void);
static  int     match(const char *, char *);
static  long    unsigned        getnum(int, int, int);
static  int     getin(void);
static  void    unget(int);
#ifdef  __FLOAT__
static  double  lstrtod(void);
#endif

static  int     ungot;          /* getin/unget char */
static  FILE    *fpin;          /* input file pointer */
static  int     pflag;          /*indicator of conversion description present */
static  int     width;          /* field width value */
static  const   char    *fmtptr;        /* format string pointer */
static  int     charcnt;        /* number of characters scanned (for %n) */
static  int     from;           /* token type we've come from */
static  int     gfail;          /* getnum() fail flag, non-zero for fail */

/*
 * Convert formatted input from given input.
 * This is the workhorse for scanf, sscanf, and fscanf.
 * Returns the number of matched and assigned input items.
 */
int
mks_vfscanf(FILE *pfin, const char *fmt, va_list ap)
{
        int     nitems;
        int     ltoken;
        int     c;
        int     modconv;        /* flag indicating conversion modifier */
        int     suppression;    /* flag to suppress conversion */

        long unsigned number;   /* return value from getnumber */

        ungot = EOF;
        fpin = pfin;
        fmtptr = fmt;
        from = 'X';
        nitems = 0;
        charcnt = 0;

        for (;;) {
                if (from == 'X') {
                        pflag = 0;
                        modconv = 0;
                        suppression = 0;
                        width = 0;
                }
                ltoken = gettoken();

                switch (ltoken) {

                case 0:
                        goto retitems;

                case MODCONVL:
                case MODCONVH:
                        switch (from) {

                        case 'A':
                        case 'D':
                        case 'P':
                                from = 'E';
                                modconv = ltoken;
                                break;
                        default:
                                from = 'X';
                                break;
                        }
                        break;

                case CONVTYPE:
                        switch (from) {

                        int     intassign;

                        case 'E':
                        case 'P':
                        case 'D':
                        case 'A':
                                from = 'X';
                                intassign = 1;
                                pflag = 0;

                                switch (lexp->name) {

                                case 'd':
                                        number = getnum(BASE10, width, SIGNED);
                                        if (gfail)
                                                goto retitems;
                                        break;
                                case 'u':
                                        number = getnum(BASE10, width, UNSIGNED);
                                        if (gfail)
                                                goto retitems;
                                        break;
                                case 'x':
                                        number = getnum(BASE16, width, SIGNED);
                                        if (gfail)
                                                goto retitems;
                                        break;
                                case 'o':
                                        number = getnum(BASE8, width, SIGNED);
                                        if (gfail)
                                                goto retitems;
                                        break;
                                case 'i':
                                        number = getnum(NOBASE, width, SIGNED);
                                        if (gfail)
                                                goto retitems;
                                        break;
                                case 'c':
                                /* 'S' dummy entry (for multibyte characters) */
                                case 'S':
                                case 's': {
                                        int gotitem = 0;
                                        char    *str;

                                        if (!suppression)
                                                str = va_arg(ap, char *);

                                        /* Input whitespace is not skipped
                                         * for %c, which implies that %c
                                         * can return whitespace.
                                         */
                                        if (lexp->name != 'c')
                                                whitespace();
                                        for (;;) {
                                                c = getin();

                                                /* Only %s and %S stop on
                                                 * whitespace.
                                                 */
                                                if (lexp->name != 'c' && isspace(c)) {
                                                        unget(c);
                                                        break;
                                                }
                                                if (c == EOF) {
                                                        if(!gotitem)
                                                                goto retitems;
                                                        break;
                                                }

                                                gotitem = 1;
                                                if (!suppression)
                                                        *str++ = c;

                                                if (width) {
                                                        if (--width == 0)
                                                                break;
                                                }
                                        }

                                        /*
                                         * ANSI C states that %c does not
                                         * terminate with a null character.
                                         */
                                        if (!suppression && lexp->name != 'c')
                                                *str = '\0';
                                        intassign = 0;
                                        break;
                                }
#ifdef  __FLOAT__
                                case 'f':
                                case 'g':
                                case 'e': {
                                        double  fresult;

                                        fresult = lstrtod();
                                        if(gfail)
                                                goto retitems;
                                        if(suppression)
                                                break;
                                        if (modconv == MODCONVL)
                                                *(double *)va_arg(ap, double *) = fresult;
                                        else
                                                *(float *)va_arg(ap, float *) = (float)fresult;
                                        /*FALLTHROUGH*/
                                }
#else   /* !__FLOAT__ */
                                case 'f':
                                case 'g':
                                case 'e':
#endif  /* __FLOAT__ */
                                default:
                                        intassign = 0;
                                        break;
                                }

                                if (suppression)
                                        break;
                                else
                                        nitems++;

                                if (intassign == 0)
                                        break;

                                switch (modconv) {

                                case MODCONVH:
                                        *(short *)va_arg(ap, short *) = (short)number;
                                        break;
                                case MODCONVL:
                                        *(long *)va_arg(ap, long *) = (long)number;
                                        break;
                                default:
                                        *(int *)va_arg(ap, int *) = (int)number;
                                        break;
                                }
                                break;
                        default:
                                from = 'X';
                                break;
                        }
                        break;

                case STAR:
                        if (from == 'P') {
                                from = 'A';
                                suppression = 1;
                        } else {
                                from = 'X';
                        }
                        break;

                case PERCENT:
                        if (from == 'P') {
                                from = 'X';
                                pflag = 0;
                                c = getin();
                                if (c != '%')
                                        goto retitems;
                        } else {
                                from = 'X';
                        }
                        break;

                case NUMBER:
                        if (from == 'P' || from == 'A') {
                                from = 'D';
                        } else {
                                from = 'X';
                        }
                        break;

                case NSCAN:
                        if (from == 'P') {
                                pflag = 0;
                                if (!suppression) {
                                        *(int *)va_arg(ap, int *) = charcnt;
                                }
                        }
                        from = 'X';
                        break;

                case BRACKET:
                        switch (from) {

                        case 'A':
                        case 'D':
                        case 'P': {
                                char *ptr;

                                pflag = 0;
                                if (width == 0)
                                        width = INT_MAX;
                                ptr = suppression ? NULL : va_arg(ap, char *);
                                if (match(fmtptr, ptr) && !feof(fpin)
                                && !suppression)
                                        nitems++;
                                while (*fmtptr++ != ']')
                                        ;
                                break;
                        }
                        default:
                                break;
                        }
                        from = 'X';
                        break;

                default:
                        c = *(fmtptr-1);
                        if (c == ' ' || c == '\t' || c == '\n' || c == '\f')
                                whitespace();
                        else {
                                c = getin();

                                if (c != *(fmtptr-1))
                                        goto retitems;
                        }
                        from = 'X';
                        break;
                }
        }
retitems:
        if (ungot != EOF) {
                ungetc(ungot, fpin);
                ungot = EOF;
        }
        return nitems==0 ? EOF : nitems;
}

static int
gettoken()
{
        char    c;

        if (*fmtptr == 0)
                return 0;       /* return 0 for end of string */

        c = *fmtptr++;

        if (pflag) {
                for(lexp=lexlist; lexp->name != 0; lexp++) {
                        if (c == lexp->name) {
                                if (lexp->type == NUMBER) {
                                        width = (int) strtol(fmtptr-1, (char **)0, BASE10);
                                        while (*fmtptr >= '0' && *fmtptr <= '9')
                                                fmtptr++;
                                } else if (c == 'c') {
                                        /* No width specified for %c, default
                                         * is one.
                                         */
                                        width = 1;
                                }
                                return lexp->type;
                        }
                }
                return -1;
        }

        if (c == '%') {
                pflag = 1;
                from = 'P';
                return gettoken();
        }
        return -1;
}

static void
whitespace()
{
        register int    c;

        do {
                c = getin();
        } while (isspace(c));

        unget(c);
}

static int
scan(int ch, const char *str, const char *estr)
{
        for (; str < estr; ++str)
                if (*str == ch)
                        return 1;

        return 0;
}

static int
match(const char *str, char *outstr)
{
        int     complement;
        int     i;
        char    start, end;
        int     c;
        const   char    *bscan, *escan;

        if (*str == '^') {
                complement = 1;
                str++;
        } else
                complement = 0;

        start = *str++;
        end = 0;
        if (*str == '-') {
                if (str[2] == ']')
                        end = str[1];
        }
        if (start > end) {
                bscan = str - 1;
                while (*str++ != ']')
                        ;
                escan = str - 1;

                for (i=0; i<width; i++) {
                        if ((c = getin()) == EOF)
                                return 0;
                        if (!scan(c, bscan, escan) ^ complement)
                                break;
                        if (outstr != NULL)
                                *outstr++ = c;
                }
        } else {
                for (i=0; i<width; i++) {
                        c = getin();
                        if (complement) {
                                if (c >= start && c <= end)
                                        break;
                                else if (outstr != NULL)
                                        *outstr++ = c;
                        } else {
                                if (c < start || c > end)
                                        break;
                                else if (outstr != NULL)
                                        *outstr++ = c;
                        }
                }
        }

        if (i < width)
                unget(c);

        if (outstr != NULL)
                *outstr = '\0';
        return (i > 1);
}

/*
 * Get a number from the input stream.
 * The base, if zero, will be determined by the nature of the number.
 * A leading 0x means hexadecimal, a leading 0 for octal, otherwise decimal.
 *
 * if the width is 0 then the max input string length of number is used.
 *
 * The sign tell us that a signed number is expected (rather than the
 *      'u' conversion type which is unsigned).
 */
static long unsigned
getnum(int base, int width, int sign)
{
        char    *s;
        char    cbuf[CBUFSIZ];                  /* char buffer for number */
        int     w;
        register int    c;
        int     neg;
        long    ret;

        gfail = 0;
        whitespace();

        if (width == 0)
                width = sizeof cbuf;

        neg = 0;
        if (sign) {
                c = getin();
                if (c == '+' || c == '-')
                        neg = c=='-' ? 1 : 0;
                else
                        unget(c);
        }

        if (base == 0) {
                base = 10;
                c = getin();
                if (c == '0') {
                        base = 8;
                        c = getin();
                        if (c == 'X' || c == 'x')
                                base = 16;
                        else
                                unget(c);
                } else
                        unget(c);
        }
        if (base == 10) {
                w = 0;
                s = cbuf;
                while (w < width && w < sizeof cbuf) {
                        c = getin();
                        switch (c) {

                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                                *s++ = c;
                                w++;
                                continue;
                        default:
                                unget(c);
                                w = width;      /* force end of loop */
                                break;
                        }
                }
                *s = '\0';
                ret = strtol(cbuf, (char **)0, 10);
                goto retn;
        }
        if (base == 8) {
                w = 0;
                s = cbuf;
                while (w < width && w < sizeof cbuf) {
                        c = getin();
                        switch (c) {

                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                                *s++ = c;
                                w++;
                                continue;
                        default:
                                unget(c);
                                w = width;      /* force end of loop */
                                break;
                        }
                }
                *s = '\0';
                ret = strtol(cbuf, (char **)0, 8);
                goto retn;
        }
        if (base == 16) {
                w = 0;
                s = cbuf;
                while (w < width && w < sizeof cbuf) {
                        c = getin();
                        c = toupper(c);
                        switch (c) {

                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                        case 'A':
                        case 'B':
                        case 'C':
                        case 'D':
                        case 'E':
                        case 'F':
                                *s++ = c;
                                w++;
                                continue;
                        default:
                                unget(c);
                                w = width;      /* force end of loop */
                                break;
                        }
                }
                *s = '\0';
                ret = strtol(cbuf, (char **)0, 16);
                goto retn;
        }

/*
 * if we get this far then a bad base was passed.
 */
        gfail = -1;

retn:
        if (*cbuf == '\0')      /* No number at all?? */
                gfail = -1;
        if (neg)
                ret = -ret;
        return ret;
}

#ifdef  __FLOAT__
static double
lstrtod()
{
        int     slen;
        int     neg, eneg;
        char    cbuf[CBUFSIZ];
        register int    c;
        register char   *sp, *s1, *s2, *s3;
        double  total, exp, tens;

        neg = eneg = 1;
        gfail = 0;

        whitespace();

        c = getin();

        if (c == '-' || c == '+')
                if (c == '-') {
                        neg = -1;
                        c = getin();
                }

        sp = s1 = cbuf;
        while (c >= '0' && c <= '9') {
                *sp++ = c;
                c = getin();
        }

        s2 = sp;
        if (c == '.') {
                c = getin();
                while (c >= '0' && c <= '9') {
                        *sp++ = c;
                        c = getin();
                }
        }

        s3 = sp;
        if (c == 'e' || c == 'E') {
                c = getin();
                if (c == '-' || c == '+')
                        if (c == '-') {
                                eneg = -1;
                                c = getin();
                        }
                while (c >= '0' && c <= '9') {
                        *sp++ = c;
                        c = getin();
                }
        }
        *sp = '\0';

        if (s1 == s2 && s2 == s3) {
                gfail = -1;
                return 0.0;
        }
        unget(c);

        /*
         * convert the three strings (integer, fraction, and exponent)
         * into a floating point number.
         */

        total = 0.0;
        tens = 1.0;
        for (sp=s2-1; sp >= s1; sp--) {
                total += (*sp -'0') * tens;
                tens *= 10.0;
        }

        tens = .1;
        for (sp=s2; sp < s3; sp++) {
                total += (*sp - '0') * tens;
                tens /= 10.0;
        }
        total *= (double)neg;

        exp = 0.0;
        tens = 1.0;
        if ((slen = strlen(s3)) > 0) {
                sp = s3 + slen - 1;
                for ( ; sp >= s3; sp--) {
                        exp += (*sp - '0') * tens;
                        tens *= 10.0;
                }
        }
        *sp = '\0';

        exp *= (double)eneg;

        total *= pow(10.0, exp);

        return total;
}
#endif  /* __FLOAT__ */

static  int
getin()
{
        int     c;

        if (ungot != EOF) {
                c = ungot;
                ungot = EOF;
        } else
                c = getc(fpin);
        charcnt++;
        return c;
}

static void
unget(int c)
{
        /* Dont' use ungetc because it doesn't work with m_fsopen */
        ungot = c;
        charcnt--;
}