[net.sources] scanargs argument list parser

thomas@utah-cs.UUCP (Spencer Thomas) (09/10/84)

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting scanargs.3l'
sed 's/^X//' <<'//go.sysin dd *' >scanargs.3l
X.TH SCANARGS 3 "AMPEX CORP."
X.SH NAME
scanargs, qscanargs - formatted conversion from command argument list
X.SH SYNOPSIS
X.B "#include <stdio.h>"
X.PP
X.B "scanargs(argc, argv, format" 
[, pointer]...
X.B )
X.br
X.B "int argc;"
X.br
X.B "char *argv[];"
X.br
X.B "char *format;"
X.SH DESCRIPTION
X.I Scanargs
reads
X.I argc
arguments from an argument list pointed to by
X.I argv.
It converts the argument list according to the format string,
and stores the results of the conversions in its parameters.
X.I Qscanargs
is a smaller and less powerful version which does not understand floating 
point.
X.PP
Scanargs expects as its parameters an argument count
X.I argc,
a pointer to an argument list 
X.I argv
(see 
X.IR exec (2)),
a control string
X.I format,
described below, and a set of
X.I pointer
arguments indicating where the converted output should be stored.
X.PP
The control string contains specifications, which are used to direct
interpretation of argument sequences.  It contains
the necessary information to describe an acceptable 
syntax for the argument list, and the expected meaning of each argument.
X.PP
If the scanning fails it will print a cryptic 
message telling why it failed, and generate a 
X.I usage
message from the control string.
X.PP
The control string is composed of two parts:
X.PP
X.B Name:\ \ 
The first characters in the string are assumed to be the calling 
name of the program being executed. This is used for generation of
usage messages, but is otherwise ignored.
If this field is a % sign, it is replaced with the contents of
X.IR argv [0]
in the message.
X.PP
X.B Conversions:\ \ 
Following the name, an optional list of conversion specifications is given,
with separating spaces.  The structure of a conversion specification:
X.RS
X.PP
X.B "label_key_conversion"
X.RE
X.PP
consists of a
X.I label
which is a string of non-space characters describing the acceptable
argument, a
X.I key
which may be either of
X.TP 4
X.B %
The argument is optional. Its absence is ignored.
X.TP 4 
X.B !
A required argument. If absent, an error return ensues.
X.PP
and a 
X.I conversion
character which indicates the interpretation of the argument; the corresponding
pointer parameter must be of a restricted type.
X.PP
The following conversion characters are supported:
X.TP 4
X.B d D
a decimal integer is expected; the corresponding parameter should be an 
X.I int
or a
X.I long
(if
X.B D
is specified) pointer.
X.TP 4
X.B o O
an octal integer is expected; the corresponding parameter should be an 
X.I int
or a
X.I long
pointer.
X.TP 4
X.B x X
a hexadecimal integer is expected; the corresponding parameter should be an 
X.I int
or a
X.I long
pointer.
X.TP 4
X.B n N
an integer numeric conversion using 
X.I C
language syntax.  Numbers beginning 
X.B 0x
are hexadecimal, numbers beginning
X.B 0
are octal, and other numbers are decimal.  Negative hex numbers must have
the minus sign
X.I following
the
X.BR 0x ,
i.e. negative 0xa would be given as 0x\-a.  The corresponding pointer
should point to an
X.I int
or a
X.I long.
X.TP 4
X.B f F
a floating point number is expected; the corresponding parameter should
be a pointer to a
X.I float
or a
X.I double.
Not available in
X.I qscanargs.
X.TP 4
X.B "x X d D o O f F"
all numeric types supported by 
X.I scanf(3S)
are legal in
X.I scanargs;
X.I qscanargs
supports all but
X.B f
and
X.B F
formats, and avoids including the large size of the
X.I scanf
routine.
X.TP 4
X.B s
a character string is expected; the corresponding parameter should be the
address of a pointer to
X.I char.
X.TP 4
X.B \-
a single character flag is expected; the corresponding parameter should
be an 
X.I int
pointer.  The occurrence of a
X.B \-
followed by the character specified in the label
will cause the setting of the least significant bit of the integer pointed to 
by the corresponding parameter.  The label may consist of up to sixteen 
(32 on a VAX) option
characters, in which case one of the bits of the integer is independantly
set to reflect which one of the flags was present. (The right most character
corresponds to the LSB of the integer)  Only one option may be chosen from
each conversion specification.  The bits which are not set will remain in
their previous state.  For example, a specification of 
X.B abc%\-
would match one of
X.B \-a \-b
or
X.B \-c
in the argument list. 
X.B \-c
would cause the corresponding variable to be set to 1, 
X.B \-b
to 2, and
X.B \-a
to 4.  (Actually, these bits would be ored in, but assuming an intial value
of 0, this is true).
X.PP
X.RS 4
The
X.B \-
may be followed immediately by more label_key_conversion specifications.
These should not be separated by blanks and should not contain any
X.B \-
specifications.  They will be processed only if the flag argument is scanned.
This allows optional specification of parameters corresponding to a flag
(e.g.
X.I \-f file
).  Corresponding arguments on the command line must appear between the flag
which introduces them and the next flag in the command line.
X.RE
X.TP 4
X.B $
This may appear only as the last specifier in the format string, and is used
to "eat up" the rest of the command arguments.  The corresponding function
argument is an
X.I int
pointer.  An index into
X.I argv
to the dividing point between the arguments which have been used, and those
which have not is returned.  This index points to the first unused command
argument.  If there is no such dividing point, an error
will be generated.
X.PP
A string or numeric conversion character may be preceded by a
X.B *
or a
X.B ,
to indicate that a list of such arguments is expected.  If
X.B ,
is used, then the AT&T proposed argument standard is followed, and a single 
string is expected, with the individual list elements separated by commas or
spaces.  Two commas in a row will produce a null entry (0 if numeric,
zero-length string if string conversion), but multiple spaces, and spaces
following a comma, are taken as a single separator.  If
X.B *
is specified, then multiple arguments are parsed to produce the list.  A
format specifier with a
X.B *
or a
X.B ,
takes two arguments.  The first is an
X.B int
pointer, the number of items in the list is returned here.  The second is a
pointer to pointer to the correct data type for the format specifier.  A
pointer to the list of arguments is returned here.
X.PP
The scanner will process the control string from left to right,
and where there are multiple conversions of the same type, they will
be assigned one to one with their order of occurrence in the argument list.
Where the order of the arguments is not ambiguous in the control string,
they may occur in any order in the argument list. (ie. A decimal number
will not be confused with a flag, but may
be confused with an octal number or another decimal number. So if an
octal and a decimal number are to be arguments, their order will determine
their conversion, while a decimal number and a flag as arguments may occur
in any order and still be converted correctly.)
X.PP
An argument list that does not match the requirements of the control
string will cause the printing of a short message telling why, and
a message telling what the correct usage is.
This usage is gleaned from the control string, and the labels are used
directly.  The labels should be both terse and descriptive!  Spaces, tabs,
and newlines in the format string will be reproduced in the usage message,
and can be used for effective prettyprinting.  A single tab (following a
newline) will indent the line directly under the command name in the usage
message.
X.PP
The
X.I scanargs
function returns 1 when the argument list matched the requirements
of the control string, and returns 0 if there was a failure.
Parameters for any conversions not matched are left untouched.
X.br
For example, the call
X.RS
X.PP
int i; double x; char *name;
X.br
scanargs(argc, argv, "program decimal%d floating%F file%s"
X.in 15
, &i, &x, &name );
X.RE
X.PP
in a C program executed by the shell command
X.RS
X.PP
X.I %
program 10 3.5397 inputfile
X.RE
X.PP
will assign to 
X.I i
the value 10, 
X.I x
the value 3.5397, and
X.I name
will point to the string "inputfile".
X.PP
If the program was executed by the shell command
X.RS
X.PP
X.I %
program  3.4 .7 inputfile
X.RE
X.PP
the following would be printed on the standard error:
X.RS
X.PP
extra arguments not processed
X.br
usage : program [decimal] [floating] [file]
X.RE
X.PP
because 3.4 matches the type of 'floating' and inputfile matches
the type of 'file', leaving .7 unmatched (it is considered a string by
scanargs, to be considered a number, it must begin with a digit).
X.PP
Finally, executing the command
X.RS
X.br
X.I %
program 10
X.RE
X.br
would assign 10 to 
X.IR i ,
leaving
X.I x
and
X.I name
unaffected.
X.PP
This call could be used for the 
X.IR diff (1)
command
X.RS
X.PP
int blanks; int flags; char *file1; char *file2;
X.br
scanargs(argc, argv, "diff b%\- efh%\- file1!s file2!s"
X.in 15
, &blanks, &flags, &file1, &file2 );
X.RE
X.PP
and would only allow one of either 
X.B "\-e \-f"
or
X.B \-h
to be chosen optionally, with 
X.B \-b 
as an independent option.
X.B File1
and
X.B file2
are both required.
The usage message for this version of
X.I diff
would be
X.RS
X.PP
usage : diff [\-b] \-{efh} file1 file2
X.RE

This call could be used for a simplified version of the
X.IR sed (1)
command
X.RS
X.PP
int efile; int noprint; char *script; char *file1; char *file2;
X.br
scanargs(argc, argv, "sed n%\- script%s f%\-editfile!s file%s"
X.in 15
, &noprint, &script, &efile, &file1, &file2 );
X.RE
X.PP
If the
X.B \-f
option is specified, then a file name must be given as the next string
argument.
The usage message for this version of
X.I sed
would be
X.RS
X.PP
usage : sed [\-n] [script] [\-f editfile] file
X.RE

X.PP
Further notes on putting together a format string:
X.PP
It is still possible for conditional arguments to be confused with
arguments which stand alone.  For this reason, it is recommended that
all flags (and associated conditional arguments) be specified first in
the scanargs format string.  This ordering is not necessary for the
command line arguments, however.  The only case which could still cause
confusion if these rules are followed is illustrated below:
X.br
X.RS
format string:	"prog d%\-num%d othernum%d"
X.br
command line:	prog \-d 9
X.RE
X.br
It is unclear whether the number 9 should be associated with the 
X.I num
parameter or the
X.I othernum
parameter.  
X.I Scanargs 
assigns it to the
X.I num
parameter.  To force it to be associated with
X.I othernum
the
command could be invoked as either
X.br
X.RS
		prog 9 \-d
X.br
or		prog \-d \-\- 9
X.RE
X.br
The 
X.B \-\-
in the second example is interpreted as a flag, thereby
terminating the scan for arguments introduced by the 
X.BR \-d .
According to the proposed standard, an argument of
X.B \-\-
is to be interpreted as terminating the optional arguments on a flag.
X.PP
Note that if the format string in the above example were
X.br
X.RS
		"prog othernum%d d%\-num%d"
X.RE
X.br
it would be impossible to assign a value to 
X.I num
without also
assigning a value to 
X.I othernum.

X.SH SEE ALSO
exec(2), scanf(3S)
X.SH DIAGNOSTICS
Returns 0 on error, 1 on success.
X.SH AUTHOR
Gary Newman \(em Ampex Corporation
X.br
Spencer W. Thomas \(em University of Utah
X.SH BUGS
By its nature a call to scanargs defines a syntax
which may be ambiguous, and although the results may be surprising, 
they are quite predictable.
The heuristic used to tell string arguments from numeric arguments is just
that.  In fact, that you can't give a number as a string argument
is sort of bogus.
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 444 scanargs.3l
	/bin/echo -n '	'; /bin/ls -ld scanargs.3l
fi
/bin/echo 'Extracting scanargs.c'
sed 's/^X//' <<'//go.sysin dd *' >scanargs.c
X/* 
 * $Header: scanargs.c,v 1.3 83/05/22 02:21:32 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.
 */

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

typedef char bool;
X/* 
 * 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; }

X/* 
 * 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();

X/* 
 * Argument list is (argc, argv, format, ... )
 */
#ifndef	QUICK
scanargs ( va_alist )
#else
qscanargs ( va_alist )
#endif
va_dcl
{
    int     argc;			/* Actual arguments */
    char  **argv;
    char   *format;
    va_list argl;

    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

    va_start( argl );
    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<argc; cnt++)
	    arg_used[cnt] = NO;
    }

    check = 0;
    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 '!': 			/* 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 '-': 		/* 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 # */
#endif
			for (cnt = optarg+1; cnt < argc; cnt++)
			{
			    ncp = argv[cnt];

			    if ( isnum( ncp, typchr, comma_list ) )
			    {
				if ( typchr == 's' )	/* string? */
				    continue;	/* don't want numbers, then */
			    }
			    else if ( *ncp == '-' )
				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<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);

    free(arg_used);
    return (1);

error: 
    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++;
    prformat (cp, NO);
    free(arg_used);
    return 0;
}

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 '\t':
	    case '\n':
		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 */
			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)
	putc ('\n', stderr);
    return (cp);
}

X/* 
 * 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
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 scanargs.c
	/bin/echo -n '	'; /bin/ls -ld scanargs.c
fi