/* * $Header: scanargs.c,v 1.7 86/11/16 03:25:48 thomas 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. * * Compiling with QUICK defined generates 'qscanargs' == * scanargs w/o floating point support; avoids huge size * of scanf. * * 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 #include #include typedef char bool; /* * 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, "msg\n"); 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 char * prformat(); static bool isnum(); static int _do_scanargs(); void scan_usage(); /* * Argument list is (argc, argv, format, ... ) */ #ifndef QUICK scanargs ( va_alist ) #else qscanargs ( va_alist ) #endif va_dcl { va_list argl; int retval; va_start( argl ); retval = _do_scanargs( 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 _do_scanargs( argl ) va_list argl; { int argc; /* Actual arguments */ char **argv; char *format; register check; /* check counter to be sure all argvs are processed */ register char *cp; register cnt; int optarg = 0; /* where optional args start */ int nopt; char tmpflg, /* temp flag */ typchr; /* type char from format string */ char c; bool * arg_used; /* array of flags */ char * malloc(); ptr aptr; /* 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 */ int * cnt_arg; /* where to stuff list count */ int list_cnt; /* how many in list */ /* These are used to build return lists */ char ** strlist; int * intlist; long * longlist; float * fltlist; double *dbllist; char *ncp; /* remember cp during flag scanning */ #ifndef QUICK char *cntrl; /* control string for scanf's */ char junk[2]; /* junk buffer for scanf's */ cntrl = "% %1s"; /* control string initialization for scanf's */ #endif argc = va_arg( argl, int ); argv = va_arg( argl, char ** ); format = va_arg( argl, char * ); arg_used = NEW( bool, argc ); if (arg_used == NULL) { fprintf(stderr, "malloc failed in scanargs, exiting\n"); exit(-1); } else { for (cnt=0; cnt 1 && !arg_used[argc-1] ) argc--; /* find last used argument */ *va_arg( argl, int * ) = argc; 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) { va_arg( argl, int * ); /* skip the arg, too */ while (*++cp && ! isspace(*cp)) if (*cp == '!' || *cp == '%') { if ( *++cp == '*' || *cp == ',' ) { cp++; va_arg( argl, int * ); } /* * Assume that char * might be a * different size, but that all * other pointers are same size. */ if ( *cp == 's' ) va_arg( argl, char * ); else 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 */ #ifndef QUICK case 'f': /* floating # */ #endif case 'D': /* long decimal # */ case 'O': /* long octal # */ case 'X': /* long hexadecimal # */ case 'N': /* long number in C syntax */ #ifndef QUICK case 'F': /* double precision floating # */ #ifdef sgi /* Fix for broken SGI IRIS floats */ if ( typchr == 'F' ) typchr = 'f'; #endif /* sgi */ #endif /* QUICK */ for (cnt = optarg+1; cnt < argc; cnt++) { ncp = argv[cnt]; if ( isnum( ncp, typchr, comma_list ) ) { ; /* it's ok, then */ } else if ( *ncp == '-' && ncp[1] != '\0' ) if ( optarg > 0 ) /* end optional args? */ { /* Eat the arg, too, if necessary */ if ( list_cnt == 0 ) if ( typchr == 's' ) va_arg( argl, char * ); else 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; /* * On pass 0, just count them. On * pass 1, null terminate each string */ for ( pass = 0; pass <= 1; pass++ ) { for ( s = ncp; *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 = ncp; } else { nopt = 0; do { /* * Need to update aptr if parsing * a comma list */ if ( comma_list && nopt > 0 ) { ncp = 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 (*ncp != '0') tmpflg = 'd'; else if (*(ncp+1) == 'x' || *(ncp+1) == 'X') { tmpflg = 'x'; ncp += 2; } else tmpflg = 'o'; if (typchr == 'N') toupper( tmpflg ); #ifndef QUICK cntrl[1] = tmpflg;/* put in conversion */ if (sscanf (ncp, cntrl, aptr, junk) != 1) ERROR (Bad numeric argument); #else if (numcvt(ncp, tmpflg, aptr) != 1) ERROR (Bad numeric argument); #endif } 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' ) free( (char *) strlist ); } else if ( cnt >= argc ) { /* Fell off end looking, so must eat the arg */ if ( typchr == 's' ) va_arg( argl, char * ); else va_arg( argl, ptr ); } break; default: /* error */ fprintf (stderr, "error in call to scanargs\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++; prformat (cp, NO); } static char * prformat (format, recurse) char *format; { register char *cp; bool required, comma_list; int list_of; 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 '!': 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 bool isnum( str, typchr, comma_list ) register char * str; char typchr; bool comma_list; { register char * allowed, * digits, * cp; bool 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; } #ifdef QUICK numcvt(str, conv, val) register char *str; char conv; int *val; { int base, neg = 0; register unsigned int d; long retval = 0; register char *digits; extern char *index(); if (conv == 'o' || conv == 'O') base = 8; else if (conv == 'd' || conv == 'D') base = 10; else if (conv == 'x' || conv == 'X') base = 16; else return 0; if (*str == '-') { neg = 1; str++; } while (*str) { if (*str >= '0' && *str < '0'+base) d = *str - '0'; else if (base == 16 && *str >= 'a' && *str <= 'f') d = 10 + *str - 'a'; else if (base == 16 && *str >= 'A' && *str <= 'F') d = 10 + *str - 'A'; else return 0; retval = retval*base + d; str++; } if (neg) retval = -retval; if (conv == 'D' || conv == 'O' || conv == 'X') *(long *) val = retval; else *val = (int) retval; return 1; } #endif QUICK