/*
* $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 */
bool exflag; /* when set, one 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++;
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;
}