/*
 * $Id: scanargs.c,v 3.0.1.3 1992/02/27 21:18:14 spencer Exp $
 *              Version 7 compatible
 *      Argument scanner, scans argv style argument list.
 *
 *      Some stuff is a kludge because sscanf screws up
 *
 *      Gary Newman - 10/4/1979 - Ampex Corp.
 *
 *      Modified by Spencer W. Thomas, Univ. of Utah, 5/81 to
 *      add args introduced by  a flag, add qscanargs call,
 *      allow empty flags.
 *
 *      If you make improvements we'd like to get them too.
 *      Jay Lepreau     lepreau@utah-20, decvax!harpo!utah-cs!lepreau
 *      Spencer Thomas  thomas@utah-20, decvax!harpo!utah-cs!thomas
 *
 *      (I know the code is ugly, but it just grew, you see ...)
 *
 * Modified by: Spencer W. Thomas
 *      Date:   Feb 25 1983
 * 1. Fixed scanning of optional args.  Now args introduced by a flag
 *    must follow the flag which introduces them and precede any other
 *    flag argument.  It is still possible for a flag introduced
 *    argument to be mistaken for a "bare" argument which occurs
 *    earlier in the format string.  This implies that flags may not
 *    be conditional upon other flags, and a message will be generated
 *    if this is attempted.
 *
 * 2. Usage message can be formatted by inserting newlines, tabs and
 *    spaces into the format string.  This is especially useful for
 *    long argument lists.
 *
 * 3. Added n/N types for "numeric" args.  These args are scanned
 *    using the C language conventions - a number starting 0x is
 *    hexadecimal, a number starting with 0 is octal, otherwise it is
 *    decimal.
 *
 *  Modified at BRL 16-May-88 by Mike Muuss to avoid Alliant STDC desire
 *  to have all "void" functions so declared.
 */

#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>

#include "netpbm/pm_c_util.h"
#include "netpbm/nstring.h"
#include "rle_config.h"
#include "rle.h"

/*
 * An explicit assumption is made in this code that all pointers look
 * alike, except possible char * pointers.
 */
typedef int *ptr;

#define YES 1
#define NO 0
#define ERROR(msg)  {fprintf(stderr, "%s\n", msg); goto error; }

/*
 * Storage allocation macros
 */
#define NEW( type, cnt )        (type *) malloc( (cnt) * sizeof( type ) )
#define RENEW( type, ptr, cnt ) (type *) realloc( ptr, (cnt) * sizeof( type ) )

static CONST_DECL char * prformat( CONST_DECL char *, int );
static int isnum( CONST_DECL char *, int, int );
static int      _do_scanargs( int argc, char **argv, CONST_DECL char *format,
                              va_list argl );
void            scan_usage( char **, CONST_DECL char * );

/*
 * Argument list is (argc, argv, format, ... )
 */
int
scanargs ( int argc, char **argv, CONST_DECL char *format, ... )
{
    va_list argl;
    int retval;
    va_start( argl, format );
    retval = _do_scanargs( argc, argv, format, argl );
    va_end( argl );
    return retval;
}

/*
 * This routine is necessary because of a pyramid compiler botch that
 * uses parameter registers in a varargs routine.  The extra
 * subroutine call isolates the args on the register stack so they
 * don't get trashed.
 */

static int
_do_scanargs( argc, argv, format, argl )
int     argc;                   /* Actual arguments */
char  **argv;
CONST_DECL char   *format;
va_list argl;
{

    int    check;                       /* check counter to be sure all argvs
                                           are processed */
    register CONST_DECL char  *cp;
    int    cnt;
    int     optarg = 0;                 /* where optional args start */
    int     nopt = 0;
    char    tmpflg,                     /* temp flag */
            typchr;                     /* type char from format string */
    char    c;
    bool  * arg_used;                   /* array of flags */
    ptr     aptr = 0;                   /* pointer to return loc */

    bool    required;
    int     excnt;                      /* which flag is set */
    unsigned int exflag;                /* How many of a set of exclusive
                                           flags is set */

    bool    list_of;                    /* set if parsing off a list of args */
    bool    comma_list;                 /* set if AT&T style multiple args */
    bool    no_usage;                   /* If set, don't print usage msg. */
    bool    help = NO;                  /* If set, always print usage. */
    int   * cnt_arg = 0;                /* where to stuff list count */
    int     list_cnt;                   /* how many in list */
    /* These are used to build return lists */
    char ** strlist = 0;
    int   * intlist = 0;
    long  * longlist = 0;
    float * fltlist = 0;
    double *dbllist = 0;
    char  * argp;                       /* Pointer to argument. */

    CONST_DECL char   *ncp;             /* remember cp during flag scanning */
    static char   cntrl[7] = "%  %1s";  /* control string for scanf's */
    char    junk[2];                    /* junk buffer for scanf's */

    /* Set up for argument counting. */
    arg_used = NEW( bool, argc );
    if (arg_used == NULL)
    {
        fprintf(stderr, "malloc failed in scanargs, exiting\n");
        exit(-1);
    }
    else
    {
        for (cnt=0; cnt<argc; cnt++)
            arg_used[cnt] = NO;
    }
    check = 0;

    /* Scan for -help in arg list. */
    for ( cnt=1; cnt<argc; cnt++ )
        if ( strcmp( argv[cnt], "-help" ) == 0 )
        {
            check += cnt;
            arg_used[cnt] = YES;
            if ( argc == 2 )
            {
                scan_usage( argv, format );
                return 0;
            }
            else
                help = YES;
        }

    /* If format string ends in @, don't print a usage message. */
    no_usage = *(format + strlen( format ) - 1) == '&';

    cp = format;
    /*
     * Skip program name
     */
    while ( *cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0' )
        cp++;

    while (*cp)
    {
        required = NO;                  /* reset per-arg flags */
        list_of = NO;
        comma_list = NO;
        list_cnt = 0;
        switch (*(cp++))
        {
            default:                    /* all other chars */
                break;
            case ' ':                   /* separators */
            case '\t':
            case '\n':
                optarg = 0;             /* end of optional arg string */
                break;

            case '(':                   /* Surrounds a comment. */
            {
                int depth = 1;          /* Count parenthesis depth. */
                while ( *cp && depth > 0 )
                    switch ( *(cp++) )
                    {
                    case '(':   depth++;                break;
                    case ')':   depth--;                break;
                    }
                break;
            }

            case '!':                   /* required argument */
                required = YES;
            case '%':                   /* not required argument */
reswitch:                               /* after finding '*' or ',' */
                switch (typchr = *(cp++))
                {
                    case ',':           /* argument is AT&T list of things */
                        comma_list = YES;
                    case '*':           /* argument is list of things */
                        list_of = YES;
                        list_cnt = 0;   /* none yet */
                        cnt_arg = va_arg( argl, int *); /* item count * here */
                        goto reswitch;  /* try again */

                    case '$':           /* "rest" of argument list */
                        while ( argc > 1 && !arg_used[argc-1] )
                            argc--;     /* find last used argument */
                        *va_arg( argl, int * ) = argc;
                        break;

                    case '&':           /* Return unused args. */
                        /* Count how many.  Always include argv[0]. */
                        for ( nopt = cnt = 1; cnt < argc; cnt++ )
                            if ( !arg_used[cnt] )
                                nopt++;
                        if ( nopt == 1 )
                            nopt = 0;   /* Special case for no args. */
                        if ( nopt > 0 )
                        {
                            strlist = NEW( char *, nopt + 1 );
                            /* Copy program name, for sure. */
                            strlist[0] = argv[0];
                            for ( nopt = cnt = 1; cnt < argc; cnt++ )
                                if ( !arg_used[cnt] )
                                {
                                    strlist[nopt++] = argv[cnt];
                                    check += cnt;
                                    arg_used[cnt] = 1;
                                }
                            strlist[nopt] = NULL;
                        }
                        else
                            strlist = NULL;     /* No args, return empty. */

                        /* Return count and arg list. */
                        *va_arg( argl, int * ) = nopt;
                        *va_arg( argl, char *** ) = strlist;
                        break;

                    case '-':           /* argument is flag */
                        if (optarg > 0)
                            ERROR("Format error: flag conditional on flag not allowed");

                    /* go back to label */
                        ncp = cp-1;     /* remember */
                        cp -= 3;
                        for (excnt = exflag = 0
                                ; *cp != ' ' && !(*cp=='-' &&(cp[-1]=='!'||cp[-1]=='%'));
                                (--cp, excnt++))
                        {
                            for (cnt = optarg+1; cnt < argc; cnt++)
                            {
                            /* flags all start with - */
                                if (*argv[cnt] == '-' && !arg_used[cnt] &&
                                        !ISDIGIT(argv[cnt][1]))
                                    if (*(argv[cnt] + 1) == *cp)
                                    {
                                        if (*(argv[cnt] + 2) != 0)
                                            ERROR ("extra flags ignored");
                                        if (exflag)
                                            ERROR ("more than one exclusive flag chosen");
                                        exflag++;
                                        required = NO;
                                        check += cnt;
                                        arg_used[cnt] = 1;
                                        nopt = cnt;
                                        *va_arg( argl, int *) |= (1 << excnt);
                                        break;
                                    }
                            }
                        }
                        if (required)
                            ERROR ("flag argument missing");
                        cp = ncp;
                        /*
                         * If none of these flags were found, skip any
                         * optional arguments (in the varargs list, too).
                         */
                        if (!exflag)
                        {
                            (void)va_arg( argl, int * );/* skip the arg, too */
                            while (*++cp && ! ISSPACE(*cp))
                                if (*cp == '!' || *cp == '%')
                                {
                                    if ( *++cp == '*' || *cp == ',' )
                                    {
                                        cp++;
                                        (void)va_arg( argl, int * );
                                    }
                                    /*
                                     * Assume that char * might be a
                                     * different size, but that all
                                     * other pointers are same size.
                                     */
                                    if ( *cp == 's' )
                                        (void)va_arg( argl, char * );
                                    else
                                        (void)va_arg( argl, ptr );
                                }
                        }
                        else
                        {
                            optarg = nopt;
                            cp++;       /* skip over - */
                        }

                        break;

                    case 's':           /* char string */
                    case 'd':           /* decimal # */
                    case 'o':           /* octal # */
                    case 'x':           /* hexadecimal # */
                    case 'n':           /* "number" in C syntax */
                    case 'f':           /* floating # */
                    case 'D':           /* long decimal # */
                    case 'O':           /* long octal # */
                    case 'X':           /* long hexadecimal # */
                    case 'N':           /* long number in C syntax */
                    case 'F':           /* double precision floating # */
#if defined(sgi) && !defined(mips)
                        /* Fix for broken SGI IRIS 2400/3000 floats */
                        if ( typchr == 'F' ) typchr = 'f';
#endif /* sgi */
                        for (cnt = optarg+1; cnt < argc; cnt++)
                        {
                            argp = argv[cnt];

                            if ( isnum( argp, typchr, comma_list ) )
                            {
                                ;       /* it's ok, then */
                            }
                            else if ( *argp == '-' && argp[1] != '\0' )
                                if ( optarg > 0 ) /* end optional args? */
                                {
                                    /* Eat the arg, too, if necessary */
                                    if ( list_cnt == 0 ) {
                                        if ( typchr == 's' )
                                            (void)va_arg( argl, char * );
                                        else
                                            (void)va_arg( argl, ptr );
                    }
                                    break;
                                }
                                else
                                    continue;
                            else if ( typchr != 's' )
                                continue;       /* not number, keep looking */

                            /*
                             * Otherwise usable argument may already
                             * be used.  (Must check this after
                             * checking for flag, though.)
                             */
                            if (arg_used[cnt]) continue;

                            /*
                             * If it's a comma-and-or-space-separated
                             * list then count how many, and separate
                             * the list into an array of strings.
                             */
                            if ( comma_list )
                            {
                                register char * s;
                                int pass;

                                /*
                                 * Copy the string so we remain nondestructive
                                 */
                                s = NEW( char, strlen(argp)+1 );
                                strcpy( s, argp );
                                argp = s;

                                /*
                                 * On pass 0, just count them.  On
                                 * pass 1, null terminate each string
                                 */
                                for ( pass = 0; pass <= 1; pass++ )
                                {
                                    for ( s = argp; *s != '\0'; )
                                    {
                                        if ( pass )
                                            strlist[list_cnt] = s;
                                        while ( (c = *s) != '\0' && c != ' ' &&
                                                c != '\t' && c != ',' )
                                            s++;
                                        if ( pass )
                                            *s = '\0';

                                        list_cnt++;     /* count separators */
                                        /*
                                         * Two commas in a row give a null
                                         * string, but two spaces
                                         * don't.  Also skip spaces
                                         * after a comma.
                                         */
                                        if ( c != '\0' )
                                            while ( *++s == ' ' || *s == '\t' )
                                                ;
                                    }
                                    if ( pass == 0 )
                                    {
                                        strlist = NEW( char *, list_cnt );
                                        list_cnt = 0;
                                    }
                                }
                            }
                            else if ( list_of )
                                list_cnt++;   /* getting them one at a time */
                            /*
                             * If it's either type of list, then alloc
                             * storage space for the returned values
                             * (except that comma-separated string
                             * lists already are done).
                             */
                            if ( list_of )
                            {
                                if ( list_cnt == 1 || comma_list )
                                    switch( typchr )
                                    {
                                        case 's':
                                            if ( !comma_list )
                                                strlist = NEW( char *, 1 );
                                            aptr = (ptr) &strlist[0];
                                            break;
                                        case 'n':
                                        case 'd':
                                        case 'o':
                                        case 'x':
                                            intlist = NEW( int, list_cnt );
                                            aptr = (ptr) &intlist[0];
                                            break;
                                        case 'N':
                                        case 'D':
                                        case 'O':
                                        case 'X':
                                            longlist = NEW( long, list_cnt );
                                            aptr = (ptr) &longlist[0];
                                            break;
                                        case 'f':
                                            fltlist = NEW( float, list_cnt );
                                            aptr = (ptr) &fltlist[0];
                                            break;
                                        case 'F':
                                            dbllist = NEW( double, list_cnt );
                                            aptr = (ptr) &dbllist[0];
                                            break;
                                    }
                                else
                                    switch( typchr )
                                    {
                                        case 's':
                                            strlist = RENEW( char *, strlist,
                                                             list_cnt );
                                            aptr = (ptr) &strlist[list_cnt-1];
                                            break;
                                        case 'n':
                                        case 'd':
                                        case 'o':
                                        case 'x':
                                            intlist = RENEW( int, intlist,
                                                             list_cnt );
                                            aptr = (ptr) &intlist[list_cnt-1];
                                            break;
                                        case 'N':
                                        case 'D':
                                        case 'O':
                                        case 'X':
                                            longlist = RENEW( long, longlist,
                                                              list_cnt );
                                            aptr = (ptr) &longlist[list_cnt-1];
                                            break;
                                        case 'f':
                                            fltlist = RENEW( float, fltlist,
                                                             list_cnt );
                                            aptr = (ptr) &fltlist[list_cnt-1];
                                            break;
                                        case 'F':
                                            dbllist = RENEW( double, dbllist,
                                                             list_cnt );
                                            aptr = (ptr) &dbllist[list_cnt-1];
                                            break;
                                    }
                            }
                            else
                                aptr = va_arg( argl, ptr );

                            if ( typchr == 's' )
                            {
                                if ( ! comma_list )
                                    *(char **)aptr = argp;
                            }
                            else
                            {
                                nopt = 0;
                                do {
                                    /*
                                     * Need to update aptr if parsing
                                     * a comma list
                                     */
                                    if ( comma_list && nopt > 0 )
                                    {
                                        argp = strlist[nopt];
                                        switch( typchr )
                                        {
                                            case 'n':
                                            case 'd':
                                            case 'o':
                                            case 'x':
                                                aptr = (ptr) &intlist[nopt];
                                                break;
                                            case 'N':
                                            case 'D':
                                            case 'O':
                                            case 'X':
                                                aptr = (ptr) &longlist[nopt];
                                                break;
                                            case 'f':
                                                aptr = (ptr) &fltlist[nopt];
                                                break;
                                            case 'F':
                                                aptr = (ptr) &dbllist[nopt];
                                                break;
                                        }
                                    }
                                    /*
                                     * Do conversion for n and N types
                                     */
                                    tmpflg = typchr;
                                    if (typchr == 'n' || typchr == 'N' ) {
                                        if (*argp != '0')
                                            tmpflg = 'd';
                                        else if (*(argp+1) == 'x' ||
                                                 *(argp+1) == 'X')
                                        {
                                            tmpflg = 'x';
                                            argp += 2;
                                        }
                                        else
                                            tmpflg = 'o';
                    }
                                    if (typchr == 'N')
                                        tmpflg = toupper( tmpflg );


                                    /* put in conversion */
                                    if ( isupper( tmpflg ) )
                                    {
                                        cntrl[1] = 'l';
                                        cntrl[2] = tolower( tmpflg );
                                    }
                                    else
                                    {
                                        cntrl[1] = tmpflg;
                                        cntrl[2] = ' ';
                                    }
                                    if (sscanf (argp, cntrl, aptr, junk) != 1)
                                        ERROR ("Bad numeric argument");
                                } while ( comma_list && ++nopt < list_cnt );
                            }
                            check += cnt;
                            arg_used[cnt] = 1;
                            required = NO;
                            /*
                             * If not looking for multiple args,
                             * then done, otherwise, keep looking.
                             */
                            if ( !( list_of && !comma_list ) )
                                break;
                            else
                                continue;
                        }
                        if (required)
                            switch (typchr)
                            {
                                case 'x':
                                case 'X':
                                    ERROR ("missing hexadecimal argument");
                                case 's':
                                    ERROR ("missing string argument");
                                case 'o':
                                case 'O':
                                    ERROR ("missing octal argument");
                                case 'd':
                                case 'D':
                                    ERROR ("missing decimal argument");
                                case 'f':
                                case 'F':
                                    ERROR ("missing floating argument");
                                case 'n':
                                case 'N':
                                    ERROR ("missing numeric argument");
                            }
                        if ( list_cnt > 0 )
                        {
                            *cnt_arg = list_cnt;
                            switch ( typchr )
                            {
                                case 's':
                                    *va_arg( argl, char *** ) = strlist;
                                    break;
                                case 'n':
                                case 'd':
                                case 'o':
                                case 'x':
                                    *va_arg( argl, int ** ) = intlist;
                                    break;
                                case 'N':
                                case 'D':
                                case 'O':
                                case 'X':
                                    *va_arg( argl, long ** ) = longlist;
                                    break;
                                case 'f':
                                    *va_arg( argl, float ** ) = fltlist;
                                    break;
                                case 'F':
                                    *va_arg( argl, double **) = dbllist;
                                    break;
                            }
                            if ( typchr != 's' && comma_list )
                                free( (char *) strlist );
                        }
                        else if ( cnt >= argc )
                        {
                            /* Fell off end looking, so must eat the arg */
                            if ( typchr == 's' )
                                (void)va_arg( argl, char * );
                            else
                                (void)va_arg( argl, ptr );
                        }
                        break;
                    default:            /* error */
                        fprintf (stderr,
                                 "scanargs: Corrupt or invalid format spec\n");
                        return 0;
                }
        }
    }

    /*  Count up empty flags */
    for (cnt=1; cnt<argc; cnt++)
        if (argv[cnt][0] == '-' && argv[cnt][1] == '-' && argv[cnt][2] == 0
            && !arg_used[cnt] )
            check += cnt;

    /* sum from 1 to N = n*(n+1)/2 used to count up checks */
    if (check != (((argc - 1) * argc) / 2))
        ERROR ("extra arguments not processed");

    /* If -help, always print usage. */
    if ( help )
        scan_usage( argv, format );

    free(arg_used);
    return 1;

error:
    if ( !no_usage )
        scan_usage( argv, format );
    free(arg_used);
    return 0;
}

void
scan_usage( argv, format )
char ** argv;
CONST_DECL char * format;
{
    register CONST_DECL char * cp;

    fprintf (stderr, "usage : ");
    if (*(cp = format) != ' ')
    {
        if ( *cp == '%' )
        {
            /*
             * This is bogus, but until everyone can agree on a name
             * for (rindex/strrchr) ....
             */
            for ( cp = argv[0]; *cp != '\0'; cp++ )
                ;                       /* find the end of the string */
            for ( ; cp > argv[0] && *cp != '/'; cp-- )
                ;                       /* find the last / */
            if ( *cp == '/' )
                cp++;
            fprintf( stderr, "%s", cp );

            cp = format + 1;            /* reset to where it should be */
        }
        while (putc (*cp++, stderr) != ' ');
    }
    else
        fprintf (stderr, "?? ");
    while (*cp == ' ')
        cp++;
    (void)prformat (cp, NO);
}

static CONST_DECL char *
prformat (format, recurse)
CONST_DECL char   *format;
int     recurse;
{
    register CONST_DECL char  *cp;
    bool    required, comma_list;
    int    list_of, depth;

    cp = format;
    if (recurse)
        putc (' ', stderr);

    required = NO;
    list_of = 0;
    comma_list = NO;
    while (*cp)
    {
        switch (*cp)
        {
            default:
                cp++;
                break;
            case ' ':
            case '\n':
            case '\t':
                /* allow annotations */
                for ( ; format < cp; format++ )
                    putc( *format, stderr );
                putc(*cp, stderr);
                format = ++cp;
                break;

            case '(':
                /* Parentheses surround an arbitrary (parenthesis
                 * balanced) comment.
                 */
                for ( ; format < cp; format++ )
                    putc( *format, stderr );
                for ( cp++, depth = 1; *cp && depth > 0; )
                {
                    /* Don't print last close paren. */
                    if ( *cp != ')' || depth > 1 )
                        putc( *cp, stderr );
                    switch( *(cp++) )
                    {
                    case '(':   depth++;                break;
                    case ')':   depth--;                break;
                    }
                }
                format = cp;
                break;

            case '!':
                required = YES;
            case '%':
reswitch:
                switch (*++cp)
                {
                    case ',':
                        comma_list = YES;
                    case '*':
                        list_of++;
                        goto reswitch;

                    case '$':           /* "rest" of argument list */
                        if (!required)
                            putc ('[', stderr);
                        for (; format < cp - 1 - list_of; format++)
                            putc (*format, stderr);
                        fputs( " ...", stderr );
                        if ( !required )
                            putc( ']', stderr );
                        break;

                    case '-':           /* flags */
                        if (!required)
                            putc ('[', stderr);
                        putc ('-', stderr);

                        if (cp - format > 2 + list_of)
                            putc ('{', stderr);
                        cp = format;
                        while (*cp != '%' && *cp != '!')
                            putc (*cp++, stderr);
                        if (cp - format > 1 + list_of)
                            putc ('}', stderr);
                        cp += 2;        /* skip !- or %- */
                        if (*cp && !ISSPACE(*cp))
                            cp = prformat (cp, YES);
                                        /* this is a recursive call */

                        cp--;   /* don't ignore next character */

                        if (!required)
                            putc (']', stderr);
                        break;
                    case 's':           /* char string */
                    case 'd':           /* decimal # */
                    case 'o':           /* octal # */
                    case 'x':           /* hexadecimal # */
                    case 'f':           /* floating # */
                    case 'D':           /* long decimal # */
                    case 'O':           /* long octal # */
                    case 'X':           /* long hexadecimal # */
                    case 'F':           /* double precision floating # */
                    case 'n':           /* numeric arg (C format) */
                    case 'N':           /* long numeric arg */
                        if (!required)
                            putc ('[', stderr);
                        for (; format < cp - 1 - list_of; format++)
                            putc (*format, stderr);
                        if ( list_of != 0 )
                        {
                            if ( comma_list )
                                putc( ',', stderr );
                            else
                                putc( ' ', stderr );
                            fputs( "...", stderr );
                        }
                        if (!required)
                            putc (']', stderr);
                        break;
                    default:
                        break;
                }
                required = NO;
                list_of = NO;
                comma_list = NO;
                if (*cp)                /* check for end of string */
                    format = ++cp;
                if (*cp && !ISSPACE(*cp))
                    putc (' ', stderr);
        }
        if (recurse && ISSPACE(*cp))
            break;
    }
    if (!recurse)
    {
        for ( ; format < cp; format++ )
            putc( *format, stderr );
        putc ('\n', stderr);
    }
    return (cp);
}

/*
 * isnum - determine whether a string MIGHT represent a number.
 * typchr indicates the type of argument we are looking for, and
 * determines the legal character set.  If comma_list is YES, then
 * space and comma are also legal characters.
 */
static int
isnum( str, typchr, comma_list )
register CONST_DECL char * str;
int typchr;
int comma_list;
{
    register CONST_DECL char *allowed, *digits, *cp;
    int hasdigit = NO;

    switch( typchr )
    {
        case 'n':
        case 'N':
            allowed = " \t,+-x0123456789abcdefABCDEF";
            break;
        case 'd':
        case 'D':
            allowed = " \t,+-0123456789";
            break;
        case 'o':
        case 'O':
            allowed = " \t,01234567";
            break;
        case 'x':
        case 'X':
            allowed = " \t,0123456789abcdefABCDEF";
            break;
        case 'f':
        case 'F':
            allowed = " \t,+-eE.0123456789";
            break;
        case 's':                       /* only throw out decimal numbers */
        default:
            allowed = " \t,+-.0123456789";
            break;
    }
    digits = allowed;
    while ( *digits != '0' )
        digits++;
    if ( ! comma_list )
        allowed += 3;                 /* then don't allow space, tab, comma */

    while ( *str != '\0' )
    {
        for ( cp = allowed; *cp != '\0' && *cp != *str; cp++ )
            ;
        if ( *cp == '\0' )
            return NO;               /* if not in allowed chars, not number */
        if ( cp - digits >= 0 )
            hasdigit = YES;
        str++;
    }
    return hasdigit;
}
