diff options
Diffstat (limited to 'urt/scanargs.c')
-rw-r--r-- | urt/scanargs.c | 948 |
1 files changed, 948 insertions, 0 deletions
diff --git a/urt/scanargs.c b/urt/scanargs.c new file mode 100644 index 00000000..416f2380 --- /dev/null +++ b/urt/scanargs.c @@ -0,0 +1,948 @@ +/* + * $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 <stdio.h> +#include <ctype.h> +#ifndef USE_STDARG +#include <varargs.h> +#else +#include <stdarg.h> +#endif + +#include "nstring.h" + +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, "%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<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; +} |