/* * $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 "rle.h" #include #include #ifndef USE_STDARG #include #else #include #endif #include "netpbm/pm_c_util.h" #include "netpbm/nstring.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 ) ) #if defined(c_plusplus) && !defined(USE_PROTOTYPES) #define USE_PROTOTYPES #endif #ifndef USE_PROTOTYPES static char * prformat(); static int isnum(); static int _do_scanargs(); void scan_usage(); #else 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 * ); #endif /* * Argument list is (argc, argv, format, ... ) */ int #ifndef USE_STDARG scanargs ( va_alist ) va_dcl #else scanargs ( int argc, char **argv, CONST_DECL char *format, ... ) #endif /* !USE_STDARG */ { va_list argl; int retval; #ifndef USE_STDARG int argc; char ** argv; CONST_DECL char *format; va_start( argl ); argc = va_arg( argl, int ); argv = va_arg( argl, char ** ); format = va_arg( argl, CONST_DECL char * ); #else va_start( argl, format ); #endif 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 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 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; }