/*************************************************************************
** ^FILE: xparse.c - library functions for the parsargs(3) package
**
** ^DESCRIPTION:
**    This file implements the following functions in the parseargs library:
**
**       init_args()  -- constructor for a command-object
**       usage()      -- pretty-print the command-usage
**       parsecntl()  -- control parsing, get/set command-attributes
**       parseargs()  -- parse arguments from a string vector
**       fparseargs() -- parse arguments from a file pointer
**       lparseargs() -- parse arguments from an arglist
**       sparseargs() -- parse arguyments in a string
**       vparseargs() -- parse arguments from a variable argument list
**
**    It should be noted that sparseargs() splits the given string up into
**    a whitespace separated series of tokens, whereas vparseargs assumes
**    that each parameter is already a single token (hence performs no
**    token splitting).
**
**    Each of these functions returns 0 upon success and non-zero otherwise
**    (except for usage() & init_args, which have no return value).
**
** ^SIDE-EFFECTS:
**    Each of the functions in the parseargs library will set the external
**    character string ProgName to be the name of the last command that was
**    operated upon by any of the library routines.
**
**    When an argument-descriptor array is first encountered by any of the
**    parseargs library routines, it is initially compiled into an inter-
**    mediate form that is more convenient to manipulate. As a direct
**    result, it is not advisable to attempt to index directly into the
**    array to manipulate one of the argument descriptors (because the
**    argdesc that you thought was there may actually be somewhere else).
**    After the array has been given its initial value(s), only parsecntl(3)
**    should be used to manipulate or query the attributes of an argument
**    descriptor.
**
** ^FILES:
**    <useful.h>
**    <parseargs.h>
**
** ^SEE_ALSO:
**    argtype(3), parseargs(1), parseargs(3)
**
** ^CAVEATS:
**    Because of the way argument parsing is implemented under UNIX, MS-DOS
**    and OS/2, option arguments which contain a leading dash (`-') (or
**    whatever the option prefix character is defined to be) may not be
**    specified as a separate argument on the command line, it must be part
**    of the same argument.  That is to say that if a program has a -f option
**    that requires a string argument, then the following:
**          -f-arg
**
**    will properly assign the string "-arg" to the option whereas the
**    following:
**          -f -arg
**
**    will be interpreted by parseargs as two option strings: the first of
**    which ("-f") is missing a required argument and the second of which
**    ("-arg") will most likely be flagged as an invalid option.
**
**    Similarly, if the user requires an ARGLIST option to take multiple
**    arguments with leading dashes then the following method must be used:
**    It is a "feature" of parseargs that ARGLIST arguments are always
**    appended to the current list of arguments for the given option. Thus,
**    if "-f" is an option taking a list of arguments, then the following
**    are all equivalent:
**          -farg1 arg2
**          -f arg1 arg2
**          -farg1 -farg2
**          -f arg1 -f arg2
**
**     Hence multiple "leading dash" arguments may specified as follows:
**          -f-dash_arg1 -f-dash_arg2  ...
**
** ^BUGS:
**    When a non-multivalued argument appears more than once on the
**    command-line then only the last value supplied is used. A problem
**    occurs however in the following scenario: suppose `-s' is an option
**    that takes an optional string argument (and suppose `-x' is some
**    boolean flag). Then if the following command-line is issued:
**
**         command  -s string  -x  -s
**
**    then, the argument flags will properly correspond to the second
**    instance of the `-s' option (namely ARGGIVEN will be set but ARGVAL-
**    GIVEN will be unset) but the value associated with the option will be
**    "string" (because the first instance overwrote the default).
**    Because of this, it may be safest to reassign the default value if
**    ARGGIVEN is set but ARGVALGIVEN is unset.
**
** ^HISTORY:
**    01/02/91 	Brad Appleton 	<brad@ssd.csd.harris.com> 	Created
**
**    04/03/91 	Brad Appleton 	<brad@ssd.csd.harris.com>
**    - fixed bug in [fvsl]parseargs() and parseargs().
**      previous parse-flags should not be reset until AFTER required
**      arguments are checked for, otherwise we may forget to prompt
**      for them if $PARSECNTL asked us to do so.
**
**    08/27/91 	Earl Chew 	<cechew@bruce.cs.monash.edu.au>
**    - split out get_description().
**    - add ProgNameLen
**    - support for non-writable strings
**    - allow sparseargs() to parse empty strings like parseargs()
**
**    04/03/91 	Brad Appleton 	<brad@ssd.csd.harris.com>
**    - changed vms_style stuff to use ps_NOTCMDLINE
***^^**********************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <useful.h>
#include "strfuncs.h"
#include "exit_codes.h"

#define PARSEARGS_PRIVATE   /* include private definitions */
#define PARSEARGS_NEXTERNS  /* exclude external declarations */
#include "parseargs.h"

#ifdef amiga_style
#  define  pa_DEFAULTS  0x000
#  define  parse_argv_style   amiga_parse
#  define  print_usage_style  amiga_usage
   EXTERN  int   amiga_parse  ARGS(( char **, ARGDESC * ));
   EXTERN  VOID  amiga_usage  ARGS(( const ARGDESC *, argMask_t ));
#endif
#ifdef ibm_style
#  define  pa_DEFAULTS  pa_ANYCASE
#  define  parse_argv_style   ibm_parse
#  define  print_usage_style  ibm_usage
   EXTERN  int   ibm_parse  ARGS(( char **, ARGDESC * ));
   EXTERN  VOID  ibm_usage  ARGS(( const ARGDESC *, argMask_t ));
#endif
#ifdef unix_style
#  define  pa_DEFAULTS  pa_FLAGS1ST
#  define  parse_argv_style   unix_parse
#  define  print_usage_style  unix_usage
   EXTERN  int   unix_parse   ARGS(( char **, ARGDESC * ));
   EXTERN  VOID  unix_usage   ARGS(( const ARGDESC *, argMask_t ));
#endif
#ifdef vms_style
#  define  pa_DEFAULTS  pa_PROMPT
#  define  parse_argv_style   vms_parse
#  define  print_usage_style  vms_usage
   EXTERN  int   vms_parse    ARGS(( char **, ARGDESC * ));
   EXTERN  VOID  vms_usage    ARGS(( const ARGDESC *, argMask_t ));
#endif


#ifdef vms
#  define  USER_VARIABLE "symbol"
#else
#  define  USER_VARIABLE "environment variable"
#endif


/***************************************************************************
** ^MACRO: SYNTAX_ERROR -  check for syntax errors & missing required arguments
**
** ^SYNOPSIS:
**    SYNTAX_ERROR(status, argd)
**
** ^PARAMETERS:
**    status
**    -- current parsing status returned by last xparsexxx() call
**
**    argd
**    --argdesc-array
**
** ^RETURN-VALUE:
**    Evaluates to TRUE if need to exit, FALSE otherwise
**
** ^ALGORITHM:
**    - if (!pa_NOCHECK) and (verify_argreqs == error) then return TRUE
**    - else return FALSE
***^^**********************************************************************/
#define  SYNTAX_ERROR(status, argd)  ( !verify_argreqs(argd, &status) )


/***************************************************************************
** ^GLOBAL-VARIABLE: ProgName, ProgNameLen
**
** ^VISIBILITY:
**    external global (visible to functions in all files)
**
** ^DESCRIPTION:
**    ProgName (which is initially NULL) will be used to point to the
**    command-name (specified on the command-line) of the command that
**    has most recently invoked a function in the parseargs library.
**    ProgNameLen will be set to the length of the string.
***^^**********************************************************************/
CONST char *ProgName = CHARNULL;
int ProgNameLen = 0;

EXTERN  VOID  syserr   ARGS((const char *, ...));
EXTERN  VOID  usrerr   ARGS((const char *, ...));
EXTERN  VOID  eprintf  ARGS((const char *, ...));

#ifdef vms_style
   EXTERN BOOL argInput        ARGS((ARGDESC *, char *, BOOL));
   EXTERN BOOL argOutput       ARGS((ARGDESC *, char *, BOOL));
#endif

#define  MAXLINE  256


/* override argument descriptor, if none given by user */
static ARGDESC  Empty_ArgDesc[] = { START_ARGUMENTS, END_ARGUMENTS };

/***************************************************************************
** ^SECTION: DEFAULT-ARGUMENTS
**    Each argdesc-array has an initial default argument list (which may be
**    reset using the pc_DEFARGS function code with parsecntl). This initial
**    default argument-list contains `?' and `H' which may be used as single
**    character keywords to display command-usage for all command-line
**    styles.  Similarly, "?", "H", and "Help" may be used as long-keywords
**    to display command-usage for all command-line styles.  In Addition,
**    for VMS style commands, the qualifiers /INPUT=file, /OUTPUT=file, and
**    /ERROR=file, may be used to redirect stdin, stdout, and stderr
**    (respectively) to a file.  For AmigaDOS style commands, the keyword
**    "ENDKWDS" may be used to disable parsing for any more keywords on
**    the command-line.
***^^**********************************************************************/
static  ARGDESC  Default_ArgDesc[] = {
   START_ARGUMENTS,

/* <name> <flags>    <type>     <valp>         <prompt>      */
   { '?', ARGHIDDEN, argUsage, __ NULL,    "? (print usage and exit)" },
   { 'H', ARGHIDDEN, argUsage, __ NULL,    "Help (print usage and exit)" },

#ifdef amiga_style
   { '-', ARGHIDDEN, argEnd,   __ NULL,    "ENDKeyWorDS" },
#endif

#ifdef vms_style
# ifdef vms
   { '<', ARGHIDDEN, argInput, __ &stdin,   "sysINPUT (redirect SYS$INPUT)" },
   { '>', ARGHIDDEN, argOutput, __ &stdout, "sysOUTPUT (redirect SYS$OUTPUT)" },
   { '%', ARGHIDDEN, argOutput, __ &stderr, "sysERROR (redirect SYS$ERROR)" },
# else
   { '<', ARGHIDDEN, argInput, __ stdin,   "sysINPUT (redirect SYS$INPUT)" },
   { '>', ARGHIDDEN, argOutput, __ stdout, "sysOUTPUT (redirect SYS$OUTPUT)" },
   { '%', ARGHIDDEN, argOutput, __ stderr, "sysERROR (redirect SYS$ERROR)" },
# endif
#endif

   END_ARGUMENTS
};


#ifdef AmigaDOS
#  define  getenv(s)   CHARNULL
#  define  envfree(s)  s = CHARNULL
#endif

#if ( defined(MSDOS) || defined(OS2) )
   EXTERN  char *getenv ARGS(( const char * ));
#  define  envfree(s)  s = CHARNULL
#endif

#ifdef unix
   EXTERN  char *getenv ARGS(( const char * ));
#  define  envfree(s)  s = CHARNULL
#endif

#ifdef vms
#  define  getenv(s)   get_symbol(s)
#  define  envfree(s)  (s) = ( !(s) ) ? CHARNULL : (free(s), CHARNULL)
#  include <descrip.h>
#  include <libdef.h>

#  define  MAXLEN 255

   /***************************************************************************
   ** ^FUNCTION: get_symbol - retrieve the value of a VMS symbol
   **
   ** ^SYNOPSIS:
   */
#  ifndef __ANSI_C__
      char *get_symbol( sym_name )
   /*
   ** ^PARAMETERS:
   */
      char *sym_name;
   /*    -- name of the symbol to retrieve
   */
#  endif  /* !__ANSI_C__ */

   /* ^DESCRIPTION:
   **    Get_symbol will lookup the named symbol and return its value
   **    as a string.
   **
   ** ^REQUIREMENTS:
   **    sym_name should correspond to the name of a pre-defined symbol.
   **
   ** ^SIDE-EFFECTS:
   **    None.
   **
   ** ^RETURN-VALUE:
   **    NULL if the symbol is not found, otherwise it copies the symbol
   **    value and returns the address of the copy (which may later be
   **    deallocated using free()).
   **
   ** ^ACKNOWLEDGEMENTS:
   **    Thanx to Jim Barbour for writing most of this code. --BDA
   **
   ** ^ALGORITHM:
   **    Trivial - just use the LIB$GET_SYMBOL system service.
   ***^^**********************************************************************/
#  ifdef __ANSI_C__
      char *get_symbol( const char *sym_name )
#  endif
   {
      unsigned long stat, lib$get_symbol();
      unsigned short buflen;
      char sym_value[ MAXLEN ];
      $DESCRIPTOR( sym_name_d, sym_name );
      $DESCRIPTOR( sym_value_d, sym_value );

      sym_value_d.dsc$w_length =  MAXLEN;
      sym_name_d.dsc$w_length = strlen( sym_name );
      stat = lib$get_symbol( &sym_name_d, &sym_value_d, &buflen, (void *)0 );
      if ( stat == LIB$_NOSUCHSYM ) {
         return  CHARNULL;
      }
      else if ( ! (stat & 1) ) {
         exit(stat);
      }
      sym_value[ buflen ] = '\0';
      return  strdup( sym_value );
   }
#endif /* vms */


/***************************************************************************
** ^FUNCTION: is_interactive - determine if a stream is "interactive"
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static BOOL is_interactive( fd )
/*
** ^PARAMETERS:
*/
   int fd;
/*    -- file descriptor associated with the input stream
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Is_interactive determines whether or not the given i/o stream is
**    associated with a terminal.
**
** ^REQUIREMENTS:
**    Fd must correspond to a valid, open, file-descriptor.
**
** ^SIDE-EFFECTS:
**    None.
**
** ^RETURN-VALUE:
**    TRUE if fd is associated with a terminal, FALSE otherwise.
**
** ^ALGORITHM:
**    Trivial - just use isatty and restore errno if necessary
***^^**********************************************************************/
#ifdef __ANSI_C__
   static BOOL is_interactive( int fd )
#endif
{
#ifdef unix
   EXTERN int isatty  ARGS((int));
   extern int errno;
   int saverr = errno;  /* save errno */

   if ( isatty(fd) )
      return  TRUE;
   else {
      errno = saverr;
      return  FALSE;
   }
#else
#ifdef vms
   EXTERN int isatty  ARGS((int));
   int ret = isatty( fd );

   if ( ret == -1 ) /* error with fd */
      syserr( "error on file descriptor #%d", fd );
   else if ( ret )
      return  TRUE;
   else
      return  FALSE;
#else
   return  TRUE;
#endif
#endif
}


/***************************************************************************
** ^FUNCTION: init_args - Initialize the command object
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   VOID init_args( argd )
/*
** ^PARAMETERS:
*/
   ARGDESC argd[];
/*    -- the array of command-arguments to initialize.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Init_args performs all the actions that are required to prepare an
**    argdesc-array for use by any of the parseargs functions. Storrage
**    is allocated and initialized and argument descriptions are compiled.
**
** ^REQUIREMENTS:
**    <argd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFFECTS:
**    The argd is initialized for use.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - compile the command name, purpose, and description if they were given
**    - if ENDOFARGS was used without STARTOFARGS, then shift each item in
**      the array down by one position.
**    - initialize the parse-flags to the default settings
**    - initialize the default argument search-list
**    - allocate and initialize the command-context
**    - for each command-line argument in argd
**      - compile the ad_prompt field and set the default initial ARGXXX flags
**      end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
   void init_args( ARGDESC argd[] )
#endif
{
   register ARGDESC *ad, *anchor;
   int  ad_count = 0;
   BOOL old_style = FALSE;
   char *description = CHARNULL, *purpose = CHARNULL;
   int  desclen;

   if ( !argd )  return;

      /* dont initialize if its already been done */
   if ( CMD_isINIT(argd) )  return;

   if ( !ARG_isEND(argd) )  old_style = TRUE;

      /* determine the argument count and preprocess each ad-entry */
   anchor = ( old_style ) ? argd : ARG_FIRST(argd);
   for ( ad = anchor ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) )  {
      ad_count++;

         /* set-up any positional args that we know of */
      if ( ARG_isPOSONLY(ad) )  BSET( arg_flags(ad), ARGPOS );

         /* set-up any default ARGNOVALs that we know of */
      if (ARG_isBOOLEAN(ad) || ARG_isPSEUDOARG(ad))
         BSET( arg_flags(ad), ARGNOVAL );

      if ( get_argdesc( (char *)arg_sname(ad), &desclen ) )
         BSET(arg_flags(ad), ARGDESCRIBED);
   }

      /* shift all the entries down one to make room for a new 1st-entry
      ** It would've been nice to just swap the 1st and last entries but
      ** I have to preserve the order that all positional parameters are
      ** given in.
      */
   if ( old_style  &&  ad_count > 0 ) {
      anchor = ad + 1;  /* save this position */
      for ( ; ad > argd ; ARG_RETREAT(ad) ) {
         memcpy( (ARBPTR)ad, (ARBPTR)(ad - 1), sizeof(ARGDESC) );
      }/*for*/
      memcpy( (ARBPTR)argd, (ARBPTR)anchor, sizeof(ARGDESC) );
   }
   else  anchor = ad;

     /* set default parse-flags */
#ifndef ibm_style
   cmd_flags(argd) = pa_DEFAULTS;
#else
   {
      char *pfx = getenv( "SWITCHAR" );
      if ( pfx  &&  *pfx == '-' )
         cmd_flags(argd) = pa_FLAGS1ST;
      else
         cmd_flags(argd) = pa_DEFAULTS;
   }
#endif

   if ( cmd_name(argd) )  cmd_name(argd) = strdup( cmd_name(argd) );
     /* if new-style, get the purpose from the command name */
   if ( !old_style  &&  cmd_name(argd) ) {
      purpose = cmd_name(argd);
   }

      /* set the program name */
   if ( ProgName  &&  *ProgName  &&  !cmd_name(argd) ) {
      cmd_name(argd) = ProgName;
   }

      /* set context */
   if ( !cmd_context(argd) )  {
      cmd_context(argd)  = (CTXDESC *) anchor;
      cmd_state(argd)    = ( old_style ) ? ps_OLDSTYLE : (ps_flags_t) 0;
      cmd_argv0(argd)    = cmd_name(argd);
      cmd_purpose(argd)  = purpose;
      cmd_ptrs(argd) = (ARGDPTRS *)malloc( sizeof(ARGDPTRS) );
      if ( !cmd_ptrs(argd) ) {
         syserr( "malloc failed in init_args()" );
      }
      if ( argd == Default_ArgDesc ) {
         cmd_defargs(argd)  = ARGDESCNULL;
      }
      else {
         if ( !CMD_isINIT(Default_ArgDesc) )  init_args( Default_ArgDesc );
         cmd_defargs(argd)  = Default_ArgDesc;
      }
      cmd_list(argd) = ARGDESCNULL;
#ifdef amiga_style
      cmd_prev(argd) = ARGDESCNULL;
#endif
   }
}


/***************************************************************************
** ^FUNCTION: reset_args - (re)set a command for parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID reset_args( argd )
/*
** ^PARAMETERS:
*/
   ARGDESC argd[];
/*    -- array or command-line arguments to be reset
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Reset_args will prepare a command for parsing. The command-state is
**    initialized and each argument is reset to "unmatched".
**
** ^REQUIREMENTS:
**    <argd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFFECTS:
**    resets the ARG flags of each argument and the command-state of argd.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - reset the command-context to be (as of yet) unparsed
**    - reset the ARG flags of the programmer & default argument descriptors
***^^**********************************************************************/

   /* arg-flags to be reset before parsing a new command-line */
#define  ARGDEFAULTS  ( ARGGIVEN | ARGVALGIVEN | ARGKEYWORD | ARGVALSEP )

#ifdef __ANSI_C__
   static void reset_args( ARGDESC argd[] )
#endif
{
   register ARGDESC *args, *ad;

   if ( !CMD_isINIT(argd) )  init_args(argd);

      /* reset the command context */
   BCLEAR( cmd_state(argd), (ps_NOFLAGS|ps_NOCMDENV|ps_NOPARSECNTL) );
   cmd_list(argd) = ARGDESCNULL;
#ifdef amiga_style
   cmd_prev(argd) = ARGDESCNULL; /* No argument requested */
#endif

      /* clear out any cruft in the argument descriptors */
   for ( args = argd ; args ; args = cmd_defargs(args) ) {
      for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
         BCLEAR( arg_flags(ad), ARGDEFAULTS );

#if ( defined(vms_style)  ||  defined(amiga_style) )
            /* vms and amiga use keywords only */
         BSET( arg_flags(ad), ARGKEYWORD );
#endif
      }/*for*/
   }/*for*/
}

#undef ARGDEFAULTS


/***************************************************************************
** ^FUNCTION: prompt_user - prompt the user for a missing argument
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static BOOL prompt_user( ad, argname )
/*
** ^PARAMETERS:
*/
   ARGDESC *ad;
/*    -- pointer to the argument to be supplied by the user
*/
   char *argname;
/*    -- name of the argument to be supplied by the user
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Prompt_user will attempt to prompt the user to supply (on standard input)
**    the value for the given argument. If the argument is a list, then each
**    item of the list should be given on a separate line (with a blank line
**    terminating the list).
**
**    No special "escaping" or translation is performed on the resulting user
**    input, hence any whitespace ro quotes will be considered as part of the
**    argument value.
**
**    If stdin is NOT associated with a terminal then no prompting is performed
**    and FALSE is returned.  If the user enters in invalid value for the
**    argument then the value is discarded and FALSE is returned (so you
**    better get it right the first time).
**
** ^REQUIREMENTS:
**    Only the first 255 characters of user input is used.
**
** ^SIDE-EFFECTS:
**    Modifies <ad> accordingly.
**
** ^RETURN-VALUE:
**    FALSE if there is an error, TRUE otherwise.
**
** ^ALGORITHM:
**    - if stdin is not connected to a terminal return FALSE
**    - if argument is not a list then
**      - get string from user
**      - attempt to convert the string
**      - return TRUE upon success and FALSE upon failure
**    - else (the argument is a list)
**      - while (user has not entered a blank line) do
**        - get string from user
**        - attempt to convert the string
**        - return FALSE upon failure (otherwise continue to loop)
**        end-while
**      end-if
***^^**********************************************************************/
#ifdef __ANSI_C__
   static BOOL prompt_user( ARGDESC *ad, const char *argname )
#endif
{
   BOOL  error = FALSE, first = TRUE;
   int   end;
   char  buf[ MAXLINE ];

   if ( !is_interactive(STDIN) )  return  FALSE;

   if ( ARG_isMULTIVAL(ad) ) {
      fprintf(stderr, "Enter one %s per-line ", argname);
      fprintf(stderr, "(enter a blank line to stop).\n");
   }

   do {  /* need repeated prompting for an ARGLIST or an ARGVEC */
      /* get argument from user */
      *buf='\0';
#ifdef vms_style
      fprintf(stderr, "\r%s> ", argname);
#else
      fprintf(stderr, "\rEnter %s: ", argname);
#endif
      fflush( stderr );
      if ( !fgets(buf, MAXLINE, stdin) )  *buf = '\0';

      /* discard the newline */
      end = strlen(buf) - 1;
      if ( end >= 0  &&  buf[end] == '\n' )
         buf[ end ] = '\0';

      /* make sure we read something! */
      if ( *buf == '\0' ) {
         if ( first ) {
            usrerr( "error - no %s given", argname );
            error = TRUE;
         }
         continue;
      }

      /* try to convert what we read (remember - buf is transient) */
      if (  (*arg_type(ad))(ad, buf, TRUE) )
         BSET(arg_flags(ad), ARGGIVEN | ARGVALGIVEN);
      else
         error = TRUE;

      first = FALSE;
   } while ( !error &&  ARG_isMULTIVAL(ad)  &&  *buf );

   return  (error) ? FALSE : TRUE;
}


/***************************************************************************
** ^FUNCTION: verify_argreqs - check for any missing required arguments
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static BOOL verify_argreqs( cmd, parse_status )
/*
** ^PARAMETERS:
*/
   ARGDESC *cmd;
/*    -- the argdesc-array of command-line arguments.
*/
   int *parse_status;
/*    -- address of current parse_status
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Verify_argreqs will reset parse_status to a success-value if it 
**    previously indicated a syntax error but pa_IGNORE is set to ignore
**    syntax error.  Verify_argreqs will then verify that any required
**    command-line arguments have been supplied (if they were not and
**    pa_PROMPT is set, then the missing values will be prompted for).
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
**    parse_status should be a pointer to the result of a previous call to
**    {f,l,s,v,}parseargs.
**
** ^SIDE-EFFECTS:
**    The arg-descs for missing arguments may be modified by prompt_user.
**    parse_status will be modified to indicate whether or not a syntax
**    error really has occurred (it will be pe_SUCCESS if all is hunky-dory).
**
** ^RETURN-VALUE:
**    FALSE if there is an error, TRUE otherwise.
**
** ^ALGORITHM:
**    - if parse_status is a syntax error but pa_IGNORE is set
**      - ignore the error by resetting the status to pe_SUCCESS
**      end-if
**    - if pa_NOCHECK is not set then check for any missing required
**      arguments (using prompt_user to get the values if pa_PROMPT is set).
***^^**********************************************************************/
#ifdef __ANSI_C__
   static BOOL verify_argreqs( ARGDESC *cmd, int *parse_status )
#endif
{
   register ARGDESC *ad;
   BOOL error;
   argName_t  s;

   if ( !CMD_isINIT(cmd) )  init_args(cmd);

   if ( *parse_status == pe_SYNTAX  &&  BTEST(cmd_flags(cmd), pa_IGNORE) ) {
      *parse_status = pe_SUCCESS;
   }
   error = ( *parse_status != pe_SUCCESS ) ? TRUE : FALSE;

   if ( !BTEST(cmd_flags(cmd), pa_NOCHECK) ) {
      for (ad = ARG_FIRST(cmd) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad)) {
         if (arg_type(ad) == argDummy)  continue;

         if ( ARG_isREQUIRED(ad)  &&  !ARG_isGIVEN(ad) ) {
               /* still didn't get a value... sigh */
            if ( ARG_isPOSITIONAL(ad) ) {
               (VOID) get_argname( arg_sname(ad), s );
               usrerr("%s required", s);
            }
            else {
#ifdef amiga_style
               (VOID) get_kwdname( arg_sname(ad), s );
               usrerr("argument required for %s keyword", s);
#endif
#ifdef ibm_style
               (VOID) get_argname( arg_sname(ad), s );
               {
                  char c, *pfx = getenv( "SWITCHAR" );
                  c = ( pfx &&  *pfx ) ? *pfx : '/';
                  usrerr("%s required for %c%c switch", s, c, arg_cname(ad));
               }
#endif
#ifdef unix_style
               (VOID) get_argname( arg_sname(ad), s );
               usrerr("%s required for %c%c flag", s, c_OPT_PFX, arg_cname(ad));
#endif
#ifdef vms_style
               (VOID) get_kwdname( arg_sname(ad), s );
               usrerr("value required for %c%s qualifier", *s_KWD_PFX, s);
#endif
            }

            if ( !error  &&  BTEST(cmd_flags(cmd), pa_PROMPT) ) {
               if ( !prompt_user(ad, s) )  error = TRUE;
            }
            else if ( !error ) {
               error = TRUE;
            }
         }/*if*/
      }/*for*/
   }/*if*/

   return  (error) ? FALSE : TRUE;
}


/***************************************************************************
** ^FUNCTION: read_flags - read bitmask-flags in a string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static argMask_t read_flags( str, c_tbl, m_tbl )
/*
** ^PARAMETERS:
*/
   char *str;
/*    -- the string to parse
*/
   char c_tbl[];
/*    -- a (NUL-terminated) array of alpha-numeric characters Each character
**    is the first letter/number of a token.  If the character is lowercase,
**    then the corresponding mask is set, if it is uppercase, the corresponding
**    mask is cleared.
*/
   argMask_t m_tbl[];
/*    -- a table of bitmasks for the given character-table. The mask to use
**       for c_tbl[i] is m_tbl[i].
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Read_flags will parse the given string for any flags present in c_tbl[].
**    When a flag is matched, its corresponding entry in m_tbl[] is set (or
**    cleared) in the currently matched set of flags.
**
**    When the given string has been completely scanned, the resulting
**    combination of masks that were matched is returned.
**
** ^REQUIREMENTS:
**    A '\0' or a ';' indicates the end of parsing. A token/mask may be negated
**    by preceding it with one of '!', '^', or '~'. Tokens must be separated by
**    one or more non-alphanumerics (other than '!', '~', and '^').
**
** ^SIDE-EFFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The combination of bitmasks indicated by the given string.
**
** ^ALGORITHM:
**    - set masks = 0
**    - for each "token" in str
**      - if token doesnt match any entries in c_tbl then continue
**      - let i = index of matched token in c_tbl
**      - if c_tbl[i] is lowercase then OR masks with m_tbl[i]
**      - if c_tbl[i] is uppercase then AND masks with NOT(m_tbl[i])
**      end-for
**    - return masks
***^^**********************************************************************/

      /* macros for parsing flags */
#define  IS_NEGCHAR(c)  ( c == '!' || c == '^' || c == '~' )
#define  SKIP_TOK(s)    while ( isalnum(*s) || *s == '_' || *s == '-' ) s++
#define  SKIP_SEP(s)    while ( *s && !IS_NEGCHAR(*s) && !isalnum(*s) ) s++
#define  NEXT_TOK(s)    SKIP_TOK(s) ; SKIP_SEP(s)
#define  TOGGLE(x)      x = (x) ? FALSE : TRUE

#ifdef __ANSI_C__
   static argMask_t read_flags(
      const char *str, const char c_tbl[], const argMask_t m_tbl[]
   )
#endif
{
   char *pos = CHARNULL;
   BOOL  negated = FALSE;
   argMask_t  mask = 0, flags = 0;

   while ( *str  &&  *str != ';' ) {
      mask = 0;
      negated = FALSE;

      while ( IS_NEGCHAR(*str) ) {
         TOGGLE(negated);
         ++str;
         SKIP_SEP(str);
      }

      if ( (pos = strchr( c_tbl, TOLOWER(*str) )) != CHARNULL ) {
         mask = m_tbl[ (int)(pos - (char *)c_tbl) ];
      }
      else if ( (pos = strchr( c_tbl, TOUPPER(*str) )) != CHARNULL ) {
         TOGGLE(negated);
         mask = m_tbl[ (int)(pos - (char *)c_tbl) ];
      }
      else {
         negated = FALSE;
      }

      NEXT_TOK(str);

      if ( mask || negated ) {
         if   (   negated  )  BCLEAR(flags, mask);
         else /* !negated */  BSET(flags, mask);
      }/*if*/
   }/*while*/

   return  flags;
}

#undef  IS_NEGCHAR
#undef  SKIP_TOK
#undef  SKIP_SEP
#undef  NEXT_TOK
#undef  TOGGLE


/***************************************************************************
** ^FUNCTION: get_usage_flags - determine user-defined usage-message format
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static argMask_t get_usage_flags( cmd )
/*
** ^PARAMETERS:
*/
   ARGDESC *cmd;
/*    -- command-structure to determine usage-flags for
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Through the use of an environment variable (or a VMS symbol), the user
**    may control the syntax and the verbosity of the command-usage messages
**    that are printed by parseargs. The desired level of verbosity may be
**    set by defining the environment variable "USAGECNTL" to be a com-
**    bination of strings (case insensitive). The value of each string con-
**    trols one of three different "modes" of behavior in the displaying
**    of usage messages:  The first "mode" is "verbose" mode, which con-
**    trols whether or not a detailed description of each argument should
**    accompany the usual command-line sysnopsis. If verbose mode is
**    "off", then only a command-line synopsis is printed (this is also
**    refferred to as "terse" mode). The other two "modes" control the
**    displaying of option syntax and long-option syntax. A mode may be
**    explicitly disabled by preceding its corresponding string with the `!'
**    or `-' character. The "modes" which correspond to the possible values of
**    the "USAGECNTL" environment variable are given by the following table.
**
**    "Quiet"
**       No usage message of any kind is displayed.
**
**    "Silent"
**       Same as Quiet.
**
**    "Paged"
**       The usage message is piped to a pager. The pager used is named by
**       the "USAGE_PAGER" environment variable. If this variable is unset
**       or empty (or is not the name of an executable program), the pager
**       named by the "PAGER" environment variable us used.  If this variable
**       is unset or empty (or is not the name of an executable program) then
**       the program "/usr/ucb/more" is used.
**
**    "Description"
**       The command description is printed.
**
**    "Terse"
**       Terse mode, just print command-line synopsis.
**
**    "Verbose"
**       Verbose mode, print descriptions for each argument
**
**    "Options"
**       Option syntax is displayed.
**
**    "LongOpts"
**       Long-option syntax is displayed.
**
**    "KeyWords"
**       Same as "LongOpts".
**
**    If the environment variable "USAGECNTL" is empty or undefined, then
**    the default usage level (which is presently "Verbose + Options")
**    will be used.
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFFECTS:
**    None.
**
** ^RETURN-VALUE:
**    The usage-flags corresponding to the value of $USAGECNTL
**
** ^ALGORITHM:
**    - If $USAGECNTL is NULL or empty, return the default flags
**    - get the value of $USAGECNTL and call read_flags to parse it.
**    - return the resulting flags.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static argMask_t get_usage_flags( const ARGDESC *cmd )
#endif
{
   register  char  *usage_env;
   argMask_t  usg_ctl = 0;
   static CONST char  usage_tokens[] = {
      'n', 'q', 's',
      'T', 'v',
      'o',
      'l', 'k',
      'd',
      'p',
      '\0'
   };
   static CONST argMask_t  usage_masks[] = {
      usg_NONE,     usg_NONE,     usg_NONE,
      usg_VERBOSE,  usg_VERBOSE,
      usg_OPTS,
      usg_LONGOPTS, usg_LONGOPTS,
      usg_DESCRIPTION,
      usg_PAGED
   };

   usage_env = getenv("USAGECNTL");
   if ( !usage_env ) {
      usg_ctl = usg_VERBOSE;
   }
   else {
      usg_ctl = read_flags( usage_env, usage_tokens, usage_masks );
   }

   envfree( usage_env );

#ifdef unix_style
      /* make sure we print at least one of options/keywords */
   if ( !BTEST(usg_ctl, usg_OPTS|usg_LONGOPTS) ) {
      if ( BTEST(cmd_flags(cmd), pa_KWDSONLY) ) {
         BSET(usg_ctl, usg_LONGOPTS);
      }
      else {
         BSET(usg_ctl, usg_OPTS);
      }
   }
#endif /* unix_style */

   return  usg_ctl;
}


#ifndef LITE

/***************************************************************************
** ^FUNCTION: get_parse_flags - determine user-defined parsing behavior
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID get_parse_flags( cmd )
/*
** ^PARAMETERS:
*/
   ARGDESC *cmd;
/*    -- command-structure to determine parse-flags for
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    The programmer may control parsing behavior through the use of
**    parsecntl(3). The user may set this behavior through the use of the
**    PARSECNTL environment variable.  By indicating any number of flags
**    (possibly negated) the user will directly modify the behavior of the
**    parseargs library. Flags may be combined by placing a `+' or `|'
**    character in between flags. A switch is negated by immediately preceding
**    it with a `!' or `-' character.  The possible "flags" are given by
**    the following table. Flags are case-insensitive.
**
**    "Prompt"
**       Prompt the user for any missing arguments that are required on the
**       command-line. No special escaping or quoting is performed on the
**       user input. Required arguments that expect a list of values will
**       be repeatedly prompted for (one item per line) until a blank line
**       (followed by a carriage return) is entered.
**
**    "Ignore"
**       Ignore any unrecognized or improperly specified command-line arguments
**       and continue execution of the program. Normally, if a required
**       argument is unmatched (or an argument is improperly specified), a
**       usage message is printed and program execution is terminated.
**
**    "OptsOnly"
**       Under UNIX, setting this flag will disable the parsing of long-option
**       syntax. This will cause all arguments starting with '+' to always be
**       treated as a positional parameter (instead of a long-option).
**
**    "KwdsOnly"
**       Under UNIX, setting this flag disables the parsing of single-character
**       options.  This will cause all arguments starting with '-' to always be
**       treated as a positional parameter (instead of an option).
**
**    "LoptsOnly"
**       Same as KwdsOnly.
**
**    "Flags1st"
**       Setting this flag causes the parseargs library to force any and all
**       non-positional arguments to be specified before any positional ones.
**       As an example, under UNIX, if this flag is SET then parseargs will
**       consider the command line "cmd -x arg" to consist of one option and
**       one positional argument; however the command line "cmd arg -x" would
**       be considered to consist of two positional arguments (the -x option
**       will be unmatched).
**
**       If this flag is UNSET, then both of the previous examples are
**       considered to consist of one option and one positional argument.
**
**    "CaseIgnore"
**       Setting this flag causes character-case to be ignored when attempting
**       to match single-character argument names (i.e. causes "-i" and "-I"
**       will be considered equivalent).
**
**    If the environment variable "PARSECNTL" is empty or undefined, then the
**    parsing behavior set by the programmer is used.  If the programmer has
**    not explicitly used parsecntl(3) to modify the parsing behavior, then
**    the default behavior will be "Flags1st" for Unix Systems,
**    "!Prompt + !Ignore" for AmigaDOS systems, "Prompt" for VMS systems,
**    and "CaseIgnore" for MS-DOS and OS/2 systems.
**
** ^REQUIREMENTS:
**    <cmd> must point to an array that has been declared using the CMD_XXXX
**    macros, or using the ENDOFARGS (and optionally STARTOFARGS) macro.
**
** ^SIDE-EFFECTS:
**    Modifies the parse-flags of <cmd> is $PARSECNTL is non-null & non-empty.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - If $PARSECNTL is NULL or empty, return
**    - Else turn off all flags that may be set by $PARSECNTL
**    - get the value of $PARSECNTL and call read_flags to parse it.
**    - set the returned flags in the cmd-structure.
**    - return the resulting flags.
***^^**********************************************************************/

   /* the combination of parse-flags that may be set via $PARSECNTL */
#define  pa_USRFLAGS \
   (pa_PROMPT|pa_IGNORE|pa_ANYCASE|pa_OPTSONLY|pa_KWDSONLY|pa_FLAGS1ST)

#ifdef __ANSI_C__
   static void get_parse_flags( ARGDESC *cmd )
#endif
{
   register argMask_t  flags = 0;
   char *parse_env;
   static CONST char  parse_tokens[] = {
      'c',
      'd',
      'f',
      'i',
      'p',
      'o',
      'l','k',
      '\0'
   };
   static CONST argMask_t  parse_masks[] = {
      pa_ANYCASE,
      pa_DEFAULTS,
      pa_FLAGS1ST,
      pa_IGNORE,
      pa_PROMPT,
      pa_OPTSONLY,
      pa_KWDSONLY, pa_KWDSONLY
   };

   if ( !CMD_isINIT(cmd) )  init_args( cmd );

   if ( (parse_env = getenv("PARSECNTL"))  &&  *parse_env ) {
      flags = read_flags( parse_env, parse_tokens, parse_masks );

         /*
         ** mask off all the flags that may be set by $PARSECNTL
         ** (including any default flags).
         */
      BCLEAR( cmd_flags(cmd), pa_USRFLAGS );

      cmd_flags(cmd) |= flags;
   }/*if*/

   envfree( parse_env );
}

#undef  pa_USRFLAGS


/***************************************************************************
** ^FUNCTION: parse_user_defaults - parse user-supplied default arguments
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static VOID parse_user_defaults( cmd )
/*
** ^PARAMETERS:
*/
   ARGDESC *cmd;
/*    -- the command-line object to parse
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Programs that use parseargs may be given default arguments under
**    Unix and PCs through the use of environment variables (symbols
**    are used for VMS systems). If a  C-program or shell-script uses
**    parseargs to implement a command named "cmd" then the environment
**    variable CMD_ARGS will be parsed for any "default" arguments before
**    the command-line is parsed.  The command-line will over-ride any
**    options that are specified in this environment variable (except that
**    ARGLISTs and ARGVECs set in "CMD_ARGS" will be appended from the
**    command-line if they are selected).
**
**    It is important to note that the contents of the CMD_ARGS environ-
**    ment variable are NOT expanded by the shell and hence any special
**    characters (such as quotes or back-slashes) will NOT be escaped or
**    removed by parseargs. Furthermore, it will not be possible to try and
**    use a tab, space, or newline character in the environment variable as
**    anything other than an argument separator.
**
**    Lastly, parts of an option specification in CMD_ARGS may NOT be
**    continued on the command-line. As an example, if -f requires an
**    argument and CMD_ARGS="-f", then the command-line "cmd  bah" will
**    NOT assign "bah" as the argument to -f but will instead complain
**    about a missing argument for -f. Similarly, if -l takes a list of
**    arguments and CMD_ARGS="-l item1 item2", then the command-line
**    "cmd  bah", will NOT assign "bah" to the end of the list containing
**    "item1" and "item2" but will instead treat "bah" as the first
**    positional parameter on the command-line.
**
** ^REQUIREMENTS:
**    <cmd> should be an array of ARGDESCs declared using the CMD_XXXX macros
**    or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFFECTS:
**    Any matched arguments have their ARGDESCs modified accordingly.
**    Also, the command-state is changed to reflect the fact that the
**    environment variable has been parsed. Also, after parsing the
**    variable, the command is partially parsed. Hence the pa_CONTINUE
**    is set to prevent arguments from being reset when the actual command
**    line is parsed.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - Get the value of the environment variable
**    - if the variable is null or empty then return.
**    - turn OFF the VARIABLE-NOT-YET-PARSED flag
**    - turn on the NOCHECK flag so we dont check for missing required
**      arguments in the variable (there may be more on the command-line).
**    - parse the string and convert any arguments matched
**    - set the NOCHECK flag back to its previous setting
**    - set the CONTINUE flag since the command-line is now partially parsed.
**    - return
***^^**********************************************************************/

#define  ENV_SUFFIX  "_ARGS"  /* suffix for env-variable with default args */

#ifdef __ANSI_C__
   static VOID parse_user_defaults( ARGDESC *cmd )
#endif
{
   int  rc;
   char      *env_args;
   argName_t  env_name;
   VOID usage();

   if ( !CMD_isINIT(cmd) )  init_args( cmd );

      /* if ignoring the <CMD>_ARGS variable then just return */
   if ( BTEST(cmd_state(cmd), ps_NOCMDENV) )  return;

      /* build the name of the environment variable */
   strncpy( env_name, ProgName, ProgNameLen );
   env_name[ProgNameLen] = 0;
   (VOID) strupr( env_name );
   strcat( env_name, ENV_SUFFIX );

      /* get the value of the environment variable,
      ** split it up into tokens, and parse it.
      */
   env_args = getenv(env_name);
   if ( env_args ) {
      char **argv = (char **)NULL;
      char *env_val = strdup( env_args );
      argMask_t  saveflags = cmd_flags(cmd);

      BSET( cmd_flags(cmd), pa_NOCHECK | pa_ARGV0 | pa_COPYF );
      BSET( cmd_state(cmd), ps_NOCMDENV );

          /* split line up into whitespace separated tokens */
      if ( !strsplit( &argv, env_val, CHARNULL ) ) {
         free( argv );
         return;
      }

#ifdef vms_style
      BSET( cmd_state(cmd), ps_NOTCMDLINE );
#endif

      rc = parse_argv_style( argv, cmd );
      free( argv );
      cmd_list(cmd) = ARGDESCNULL;  /* dont allow lists to continue on */
#ifdef amiga_style
      cmd_prev(cmd) = ARGDESCNULL;
#endif

#ifdef vms_style
      BCLEAR( cmd_state(cmd), ps_NOTCMDLINE );
#endif

         /* check for errors */
      if ( rc  &&  !BTEST(cmd_flags(cmd), pa_IGNORE) ) {
         eprintf( "%.*s: syntax-error in %s \"%s\".\n",
                  ProgNameLen, ProgName, USER_VARIABLE, env_name );
         eprintf( "\t%s = \"%s\"\n\n", env_name, env_args );
         free( env_val );
         usage( cmd );
         exit( exit_SYNTAX );
      }

      free( env_val );
      cmd_flags(cmd) = (saveflags | pa_CONTINUE);
   }

   envfree( env_args );
}

#undef ENV_SUFFIX

#endif /* ! LITE */

/***************************************************************************
** ^FUNCTION: parse_init -- initialize an ARGDESC for parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static ARGDESC *parse_init( argdp )
/*
** ^PARAMETERS:
*/
   ARGDESC *argdp[];
/*    -- pointer to the argument descriptor array.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Parse_init will perform the usual pre-parsing actions such as checking
**    $PARSECNTL, parsing $<NAME>_ARGS, and resetting the command-line.
**
** ^REQUIREMENTS:
**    <argdp> should point to an array of ARGDESCs declared using the CMD_XXXX
**    macros or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFFECTS:
**    Initialize argd and parses any default arguments.
**
** ^RETURN-VALUE:
**    pointer to the arg-descriptors
**
** ^ALGORITHM:
**    - check for a NULL argdesc-array
**    - initialize the command-object if necessary
**    - set ProgName
**    - read $PARSECNTL
**    - reset the command-line if necessary (and parse $<NAME>_ARGS)
**    - return the command-object
***^^**********************************************************************/
#ifdef __ANSI_C__
   static ARGDESC *parse_init( ARGDESC *argdp[] )
#endif
{
   register ARGDESC *cmd;
   BOOL  unnamed = FALSE;

      /* allow null argument descriptor */
   if ( !(*argdp) )  *argdp = Empty_ArgDesc;

      /* initialize command-structure */
   if ( !CMD_isINIT(*argdp) )  init_args( *argdp );
   cmd = *argdp;

      /* save the name of this program (for error messages) */
   if ( cmd_argv0(cmd)  &&  *(cmd_argv0(cmd)) ) {
      ProgName = cmd_argv0(cmd);
      ProgNameLen = get_argpfx(ProgName);
   }
   else if ( cmd_name(cmd)  &&  *(cmd_name(cmd)) ) {
      ProgName = cmd_name(cmd);
      ProgNameLen = get_argpfx(ProgName);
   }
   else {
      unnamed = TRUE;
   }

#ifndef LITE
      /* read PARSECNTL environment variable */
   if ( !BTEST(cmd_state(cmd), ps_NOPARSECNTL) ) {
      get_parse_flags(*argdp);
   }
#endif

      /* reset argd if necessary */
   if ( !BTEST(cmd_flags(cmd), pa_CONTINUE) )  {
      reset_args( *argdp );

#ifndef LITE
         /* get any options from <CMDNAME>_ARGS environment variable */
      if ( !BTEST(cmd_flags(cmd), pa_NOCMDENV)  &&  !unnamed )  {
         parse_user_defaults( *argdp );
      }
#endif
   }

   return  *argdp;
}


/***************************************************************************
** ^FUNCTION: usage -- print a usage message
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   VOID usage( argd )
/*
** ^PARAMETERS:
*/
   ARGDESC argd[];
/*    -- the description of expected arguments.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Given an argdesc array, usage will print the usage for the given
**    command in the format specified by the user's USAGECNTL environment
**    variable.
**
** ^REQUIREMENTS:
**    <argd> should be an array of ARGDESCs declared using the CMD_XXXX macros
**    or with the ENDOFARGS (and possible STARTOFARGS) macros.
**
** ^SIDE-EFFECTS:
**    Prints on stderr.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - initialize the command-object
**    - set ProgName
**    - read $USAGECNTL
**    - call <os>_usage()
***^^**********************************************************************/
#ifdef __ANSI_C__
   void usage( const ARGDESC argd[] )
#endif
{
   register CONST ARGDESC *cmd;
   argMask_t  usg_ctl = 0;

      /* allow null argument descriptor */
   if ( !argd )  argd = Empty_ArgDesc;

   if ( !CMD_isINIT(argd) )   init_args( (ARGDESC *)argd );
   cmd = argd;

   if ( cmd_argv0(cmd) && *(cmd_argv0(cmd)) ) {
      ProgName = cmd_argv0(cmd);
      ProgNameLen = get_argpfx(ProgName);
   }
   else if ( cmd_name(cmd) && *(cmd_name(cmd)) ) {
      ProgName = cmd_name(cmd);
      ProgNameLen = get_argpfx(ProgName);
   }

   usg_ctl = get_usage_flags(cmd);
   if ( !BTEST(usg_ctl, usg_NONE) )  print_usage_style( argd, usg_ctl );
}


/***************************************************************************
** ^FUNCTION: parsecntl - control various aspects of command-line parsing
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int  parsecntl( argd, cntl, mode, va_alist )
/*
** ^PARAMETERS:
*/
   ARGDESC *argd;
/*    --  The command-object to operate upon
*/
   parsecntl_t cntl;
/*    --  The control-code corresponding to the desired operation
*/
   parsemode_t mode;
/*    --  The mode of the operation (read, write, or both)
*/
   va_dcl
/*    -- any further args followed by the item to be read/written
*/
#endif  /* !__ANSI_C__ */
  
/* ^DESCRIPTION:
**    Parsecntl will read and/or write the desired attributes of the given
**    command-object. The attributes to be operated upon are specified by
**    the second parameter to parsecntl. The desired mode (read, write, or
**    both) are specified by the third parameter to parsecntl. If the
**    operation to be performed is pc_ARGFLAGS, then the fourth argument to
**    parsecntl should be the keyword name of the argument whose flags are to
**    be retrieved.  The last parameter to parsecntl is always the object to
**    contain the attribute(s) to be read/written.  If the attribute(s) are
**    to be read (regardless of whether or not they are also being changed)
**    then the last argument should be a pointer to an object, otherwise the
**    last argument should be the object itself.
**
**    If mode is pc_READ, then the desired attributes are copied into the
**    object pointed to by the last parameter. If the mode is pc_WRITE, then
**    the attributes from the last parameter are copied to the command-
**    object. If mode is pc_RDWR, then the attributes pointed to by the last
**    parameter are copied to the command-object, and then the previous
**    value of these attributes (before they were overwritten) is copied
**    into the object pointed to by the last parameter.
**
**    If cntl is pc_ARGFLAGS, then the only valid mode pc_READ. All other
**    attributes may be written or read by parsecntl.
**
** ^REQUIREMENTS:
**    If mode is READ or READ+WRITE then the last argument should be the
**    address of the desired object, otherwise it should be the object itself.
**
** ^SIDE-EFFECTS:
**    None if the mode is READ, otherwise, the desired attibutes are (re)set
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_DEFARGS
**       -- an attempt (using parsecntl()) was made to change the
**          default arg-search list of a command to point to an argdesc-array
**          which already has the given command on its default arg-search list
**          (which would cause an infinite loop when attempting to match an
**          unknown command-line argument).
** 
**    pe_NOMATCH
**       -- unable to match the named argument for the pc_ARGFLAGS cntl
** 
**    pe_BADMODE
**       -- bad mode for given command in parsecntl()
** 
**    pe_BADCNTL
**       -- bad command for parsecntl
**
** ^ALGORITHM:
**    - if cntl is pc_ARGFLAGS
**      - if mode is pc_WRITE or pc_RDWR then return pe_BADMODE
**      - get the argument name and try to match it.
**      - if no match, then return pe_NOMATCH
**      - copy the flags for the matched argument
**      - return pe_SUCCESS
**    - else if cntl is pc_PARSEFLAGS
**      - save the current parseflags.
**      - if requested, (re)set the current flags
**      - if requested, copy the old flags
**      - return pe_SUCCESS
**    - else if cntl is pc_DEFARGS
**      - save the current default-args
**      - make sure the given default-args does not already contain argd
**      - if the above assertion failed, return pe_DEFARGS
**      - if requested, (re)set the default-args
**      - if requested, copy the old default-args
**      - return pe_SUCCESS
**    - else ( cntl must be in {pc_NAME, pc_PURPOSE, pc_DESCRIPTION} )
**      - save the current setting of the attribute-string
**      - if requested, (re)set the current attribute-string
**      - if requested, copy the old attribute-string
**      - return pe_SUCCESS
**      endif
***^^**********************************************************************/

   /*
   ** define some convenience macros to determine if we are
   ** reading/writing from/to the argdesc-array.
   */
#define isREADING(pmode)  (pmode == pc_READ  || pmode == pc_RDWR)
#define isWRITING(pmode)  (pmode == pc_WRITE || pmode == pc_RDWR)

#ifdef __ANSI_C__
   int parsecntl( ARGDESC *argd, parsecntl_t cntl, parsemode_t mode, ... )
#endif
{
   register ARGDESC  *cmd;
   register int  rc = pe_SUCCESS;
   va_list   ap;

   if ( !argd )   return   rc;
   if ( !CMD_isINIT(argd) )   init_args(argd);
   cmd = argd;
   VA_START( ap, mode );   /* get argument to match */

      /* now figure out what to do and go do it! */
   switch( cntl ) {
      case  pc_ARGFLAGS :  /* get/set arg-flags */
         {
            register ARGDESC *ad, *args;
            char *name = VA_ARG( ap, char * );
            argMask_t  *argflags;
            BOOL  is_match = FALSE;

               /* first we have to find the argument whose flags we need */
            for (args = argd ; args  &&  !is_match ; args = cmd_defargs(args)) {
               for (ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad)) {
                  if ( arg_type(ad) == argDummy )  continue;

                  if ( match(name, arg_sname(ad)) == 0 ) {
                     is_match = TRUE;
                     break;
                  }/*if*/
               }/*foreach arg*/
            }/*foreach argdesc*/

            if ( !is_match ) {
               VA_END(ap);
               return  pe_NOMATCH;
            }

               /* now that we found it - retrieve the argument flags */
            if ( isREADING(mode) ) {
              argflags = VA_ARG( ap, argMask_t * );
              *argflags = arg_flags(ad);
            }
            else {
              rc = pe_BADMODE;  /* parsecntl() wont set ARGFLAGS */
            }
         }/*block*/
         break;

      case  pc_PARSEFLAGS : /* get/set the parse-flags */
         {
            argMask_t *pflags, flags;

               /* get value from call-stack (dependent on the mode) */
            if ( isREADING(mode) ) {
              pflags = VA_ARG( ap, argMask_t * );
              flags = cmd_flags(cmd);
            }
            else {
              flags = (argMask_t) VA_ARG( ap, int );
              pflags = &flags;
            }

               /* perform the desired action(s) */
            if ( isWRITING(mode) )  cmd_flags(cmd) = *pflags;
            if ( isREADING(mode) )  *pflags = flags;
         }/*block*/
         break;

      case  pc_DEFARGS : /* get/set the default arguments */
         {
            ARGDESC **pdefargd, *defargd;

               /* get value from call-stack (dependent on the mode) */
            if ( isREADING(mode) ) {
               pdefargd = VA_ARG( ap, ARGDESC ** );
               defargd = cmd_defargs(cmd);
            }
            else {
               defargd = VA_ARG( ap, ARGDESC * );
               pdefargd = &defargd;
            }

            if ( isWRITING(mode) ) {
               ARGDESC *args;

               if ( !CMD_isINIT(*pdefargd) )  init_args( *pdefargd );

                  /* make sure we are not on the default-argdesc's
                  ** default-argument hierarchy (or an infinite loop
                  ** will result).
                  */
               for ( args = *pdefargd; rc && args; args = cmd_defargs(args) ) {
                  if ( args == cmd )  rc = pe_DEFARGS;
               }
               if ( !rc )  cmd_defargs(cmd) = *pdefargd;  /* set new defaults */
            }/*if*/

            if ( isREADING(mode) )  *pdefargd = defargd;
         }
         break;

      case  pc_NAME :  /* get/set name */
      case  pc_PURPOSE :  /* get/set purpose */
      case  pc_DESCRIPTION :  /* get/set description */
         {
            CONST char *str, **pstr;

               /* get value from call-stack (dependent on the mode) */
            if ( isREADING(mode) ) {
               pstr = VA_ARG( ap, CONST char ** );
               if      ( cntl == pc_NAME ) {
                  if ( !BTEST(cmd_state(cmd), ps_USERNAME|ps_FREENAME) ) {
                     if ( cmd_name(argd) ) {
                        int  n;
                        char  *p;

                        n = get_argpfx( (char *)cmd_name(argd) );
                        p = (char *)malloc( n+1 );
                        if ( !p ) {
                           syserr( "malloc failed in parsecntl()" );
                        }
                        strncpy(p, (char *)cmd_name(argd), n);
                        p[n] = 0;
                        cmd_name(argd) = p;
                        BSET(cmd_state(cmd), ps_FREENAME);
                     }
                  }
                  str = cmd_name(cmd);
               }
               else if ( cntl == pc_PURPOSE ) {
                  if ( !BTEST(cmd_state(cmd), ps_USERPURPOSE|ps_FREEPURPOSE) ) {
                     if ( cmd_purpose(argd) ) {
                        int  n;
                        char  *p, *q;

                        p = get_argdesc( (char *)cmd_purpose(argd), &n );
                        if ( p ) {
                           q = (char *)malloc( n+1 );
                           if ( !q ) {
                              syserr( "malloc failed in parsecntl()" );
                           }
                           strncpy(q, p, n);
                           q[n] = 0;
                           p = q;
                           BSET(cmd_state(cmd), ps_FREEPURPOSE);
                        }
                        cmd_purpose(cmd) = p;
                     }
                  }
                  str = cmd_purpose(cmd);
               }
               else /* cntl == pc_DESCRIPTION */  str = cmd_description(cmd);
            }
            else {
               str = VA_ARG( ap, CONST char * );
               pstr = &str;
            }

               /* perform the desired action(s) */
            if ( isWRITING(mode) )  {
               if ( cntl == pc_NAME ) {
                  if ( BTEST(cmd_state(cmd), ps_FREENAME) )
                     free( cmd_name(cmd) );
                     BCLEAR( cmd_state(cmd), ps_FREENAME );
                     BSET( cmd_state(cmd), ps_USERNAME );
                     cmd_name(cmd) = *pstr;
                  }
               else if ( cntl == pc_PURPOSE ) {
                  if ( BTEST(cmd_state(cmd), ps_FREEPURPOSE) ) {
                     free( cmd_purpose(cmd) );
                  }
                  BCLEAR( cmd_state(cmd), ps_FREEPURPOSE );
                  BSET( cmd_state(cmd), ps_USERPURPOSE );
                  cmd_purpose(cmd) = *pstr;
               }
               else /* cntl == pc_DESCRIPTION */  cmd_description(cmd) = *pstr;
            }
            if ( isREADING(mode) )  *pstr = str;
         }/*block*/
         break;

      default :
         rc = pe_BADCNTL;
         break;
   }/*switch*/

   VA_END( ap );
   return   rc;
}

#undef  isREADING
#undef  isWRITING


#ifndef LITE

/***************************************************************************
** ^FUNCTION: sparseargs - parse arguments in a string
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int sparseargs( str, argd )
/*
** ^PARAMETERS:
*/
   char    *str;
/*    -- string to parse
*/
   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */
  
/* ^DESCRIPTION:
**    Given a single string and an argdesc array, sparseargs will parse
**    arguments from a string in the same manner as parseargs.
**    Sparseargs will split the given string up into a vector of whitespace
**    separated tokens and then attempt to parse the resultant vector as if
**    it were given as argv[] on the command-line.  NO special treatment is
**    given to characters such as single-quotes, double-quotes, or anything
**    else. Sparseargs will always assume that any whitespace characters are
**    intended as argument separators.
**
** ^REQUIREMENTS:
**    <str> should be non-NULL and non-empty
**
** ^SIDE-EFFECTS:
**    <str> is modified by strsplit().
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- If a syntax error occurs in <str>
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - split string up into a vector of tokens
**    - call parse init and then parse the arguments
**    - if syntax-error, print usage and exit
**    - restore parse-flags
**    - return the status from parsing the vector
***^^**********************************************************************/
#ifdef __ANSI_C__
   int sparseargs( char *str, ARGDESC *argd )
#endif
{
   argMask_t  saveflags;
   char **argv;
   int  rc = 0;

   if ( !argd )  return  pe_SUCCESS;

   if ( !CMD_isINIT(argd) )  init_args(argd);

      /* save old flags & initialize set parse flags */
   saveflags = cmd_flags(argd);
   BSET(cmd_flags(argd), pa_ARGV0);

      /* split line up into whitespace separated tokens */
   (void) strsplit( &argv, str, CHARNULL );

#ifdef vms_style
   BSET( cmd_state(argd), ps_NOTCMDLINE );
#endif

   rc = parse_argv_style( argv, parse_init( &argd ) );
   free( argv );

#ifdef vms_style
   BCLEAR( cmd_state(argd), ps_NOTCMDLINE );
#endif

      /* scan for missing required arguments */
   if ( SYNTAX_ERROR(rc, argd) ) {
      fputc( '\n', stderr );
      usage( argd );
      exit( exit_SYNTAX );
   }

      /* reset previous parse flags */
   cmd_flags(argd)  =  saveflags;

   return  rc;
}


/***************************************************************************
** ^FUNCTION: fparseargs - parse arguments from a file
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int fparseargs( fp, argd )
/*
** ^PARAMETERS:
*/
   FILE    *fp;
/*    -- pointer to file to read (must already be open)
*/
   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Given a readable input stream and an argdesc array, fparseargs will
**    parse arguments in a file in the same manner as parseargs.  A
**    maximum-line length of 255 characters is imposed.  NO "escaping" of
**    any kind is performed. Comments of a limited form are permitted: if
**    the first non-whitespace character on a line is a '#' (or '!' for VMS)
**    then that entire line is considered a comment and is ignored.  If a
**    value is provided for an argument that is NOT a list or a vector, then
**    the value MUST be on the same line as the argument (in other words,
**    "-v val" is fine but "-v\nval" is a not).
**
** ^REQUIREMENTS:
**    <fp> should be non-NULL, already opened-for-reading, file-pointer
**
** ^SIDE-EFFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs in the file
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - add pa_NOCHECK to current parse-flags (dont check until eof)
**    - call parse init
**    - parse the first line of the file
**    - add pa_CONTINUE to current parse-flags
**    - parse the rest of the file
**    - restore parse-flags
**    - now check for missing args if required
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/

#ifdef vms_style
#  define  c_COMMENT  '!'
#else
#  define  c_COMMENT  '#'
#endif

#ifdef __ANSI_C__
   int fparseargs( FILE *fp, ARGDESC *argd )
#endif
{
   int  rc;
   char  text[ MAXLINE ];
   argMask_t  saveflags;

   if ( !argd )  return  0;

   if ( !CMD_isINIT(argd) )  init_args(argd);

      /* We want the following scenario for the various calls to sparseargs():
      ** assume there are N lines in the input file. Then there will be N
      ** calls to sparseargs(). For all calls, we want pa_ARGV0 and pa_COPYF
      ** to be ON. Then for the ALL but the very last call, we want pa_NOCHECK
      ** to be off. For all but the very first call - we want pa_CONTINUE to
      ** be turned ON. So we have the following table:
      **
      **     Parse     ||           invocation of sparseargs
      **      Flag     ||   0    |     1     |     2 .. N-1    |     N
      **   ============++========+===========+=================+=============== 
      **      ARGV0    ||   on   |    on     |       on        |     on
      **   ------------++--------+-----------+-----------------+--------------- 
      **      COPYF    ||   on   |    on     |       on        |     on
      **   ------------++--------+-----------+-----------------+--------------- 
      **      NOCHECK  ||   on   |    on     |       on        |     OFF
      **   ------------++--------+-----------+-----------------+--------------- 
      **      CONTINUE ||   OFF  |    on     |       on        |     on
      **   ============++========+===========+=================+=============== 
      */
      
      /* save old flags & initialize parse flags for first call */
   saveflags = cmd_flags(argd);
   BSET(cmd_flags(argd), pa_ARGV0 | pa_NOCHECK | pa_COPYF);

   while ( !feof( fp ) ) {
      if ( !fgets( text, MAXLINE, fp ) ) {
         if ( ferror( fp ) )  {
            cmd_flags(argd)  =  saveflags;
            return  pe_SYSTEM;
         }
      }/*if*/

         /* trim leading and trailing whitespace and check for comments */
      (VOID) strtrim( text, CHARNULL );
      if ( !text  ||  !(*text)  ||  *text == c_COMMENT )  continue;

      rc = sparseargs( text, argd );

         /* set up parseflags for next call */
      if ( !BTEST(cmd_flags(argd), pa_CONTINUE) ) {
         BSET(cmd_flags(argd), pa_CONTINUE);
      }
   }/*while !EOF*/

   if ( !BTEST(saveflags, pa_NOCHECK) )  BCLEAR(cmd_flags(argd), pa_NOCHECK);

      /* scan for missing required args */
   if ( SYNTAX_ERROR(rc, argd) ) {
      fputc('\n', stderr);
      usage( argd );
      exit( exit_SYNTAX );
   }

      /* reset previous parse flags */
   cmd_flags(argd)  =  saveflags;

   return  rc;
}

#undef  c_COMMENT


/***************************************************************************
** ^FUNCTION: lparseargs - parse arguments from a list
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int lparseargs( argls, argd )
/*
** ^PARAMETERS:
*/
   ArgList *argls;
/*    -- linked list of args to parse
*/
   ARGDESC *argd;
/*    -- pointer to argument descriptor table
*/
#endif  /* !__ANSI_C__ */
  
/* ^DESCRIPTION:
**    Given an ArgList and an argdesc array, lparseargs will parse arguments
**    in an ArgList in the same manner as parseargs.
**
** ^REQUIREMENTS:
**    <argls> should be an ArgList of strings
**
** ^SIDE-EFFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - make a vector out of the ArgList
**    - call parse init
**    - restore parse-flags
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
   int lparseargs( ArgList *argls, ARGDESC *argd )
#endif
{
   int   i, argc = 0, rc = 0;
   char **argv = (char **)NULL;
   argMask_t   saveflags;
   register    ArgList *ls;

   if ( !argd )  return  0;
   if ( !CMD_isINIT(argd) )   init_args(argd);

      /* make 1st pass to count the args */
   for ( ls = argls; ls ; ls = L_NEXT(ls) ) {
      argc++;
   }

      /* allocate a NULL terminated arg-vector */
   argv = (char **)malloc( (argc + 1) * sizeof(char *) );
   if ( !argv )  return  pe_SYSTEM;
   argv[ argc ] = CHARNULL;

      /* make 2nd pass to assign the elements of the vector */
   for ( ls = argls, i = 0 ; ls ; ls = L_NEXT(ls), i++ ) {
      argv[i] = L_STRING(ls);
   }

#ifdef vms_style
   BSET( cmd_state(argd), ps_NOTCMDLINE );
#endif

      /* parse the list */
   saveflags = cmd_flags(argd);
   BSET(cmd_flags(argd), pa_ARGV0);
   rc = parse_argv_style( argv, parse_init( &argd ) );
   free( argv );

#ifdef vms_style
   BCLEAR( cmd_state(argd), ps_NOTCMDLINE );
#endif

      /* scan for missing required arguments */
   if ( SYNTAX_ERROR(rc, argd) ) {
      fputc( '\n', stderr );
      usage( argd );
      exit( exit_SYNTAX );
   }

      /* reset previous parse-flags */
   cmd_flags(argd) = saveflags;

   return   rc;
}


/***************************************************************************
** ^FUNCTION: vparseargs - parse a variable-argument list
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int vparseargs( argd, argc, va_alist )
/*
** ^PARAMETERS:
*/
   ARGDESC  *argd;
/*    -- 
*/
   int  argc;
/*    -- number of arguments to parse
*/
   va_dcl
/*    -- the variable-list of arguments to parse
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Vparseargs takes an argdesc array, the number of arguments to parse,
**    and a (possibly NULL terminated) list of argument-strings and parses
**    them in the same manner as parseargs.  Unlike sparseargs,
**    vparseargs assumes that all parameters are already split up into
**    tokens, hence any whitespace characters contained in any of the
**    string-parameters are used as is (and will be considered a part of
**    an argument name or value).
**
**
** ^REQUIREMENTS:
**    argc must contain the number of arguments to be parsed (NOT including
**    any terminating NULL pointer).  If a NULL pointer is given as one of
**    the arguments, and this NULL pointer appears before argc indicated
**    the last argument would appear, then the NULL pointer will end the
**    the list of arguments and argc is ignored.
**    
** ^SIDE-EFFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - save current parse-flags
**    - add pa_ARGV0 to current parse-flags
**    - make a vector out of the variable list
**    - call parse init
**    - restore parse-flags
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
   int vparseargs( ARGDESC *argd, int argc, ... )
#endif
{
   register char   *arg;
   int   i, rc = 0;
   argMask_t   saveflags;
   char **argv = (char **)NULL;
   va_list   ap;

   if ( !argd )   return   0;
   if ( !CMD_isINIT(argd) )   init_args(argd);

   saveflags = cmd_flags(argd);
   BSET(cmd_flags(argd), pa_ARGV0 | pa_COPYF);

      /* allocate a NULL terminated arg-vector */
   argv = (char **) malloc( (argc + 1) * sizeof(char *) );
   if ( !argv )  return  pe_SYSTEM;
   argv[ argc ] = CHARNULL;

      /* assign the string into the array */
   VA_START(ap, argc);
   for ( i = 0; i < argc  &&  (arg = VA_ARG(ap, char *)) ; i++ ) {
      argv[i] = arg;
   }
   VA_END(ap);

#ifdef vms_style
   BSET( cmd_state(argd), ps_NOTCMDLINE );
#endif

      /* parse the arguments */
   rc = parse_argv_style( argv, parse_init( &argd ) );
   free( argv );

#ifdef vms_style
   BCLEAR( cmd_state(argd), ps_NOTCMDLINE );
#endif

      /* scan for missing required arguments */
   if ( SYNTAX_ERROR(rc, argd) ) {
      fputc( '\n', stderr );
      usage( argd );
      exit( exit_SYNTAX );
   }

      /* reset previous parse-flags */
   cmd_flags(argd) = saveflags;

   return   rc;
}

#endif /* ! LITE */


/***************************************************************************
** ^FUNCTION: parseargs -- parse an argument vector
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int parseargs( argv, argd )
/*
** ^PARAMETERS:
*/
   char  *argv[];
/*    -- pointer to the argument vector as passed to main().
*/
  ARGDESC argd[];
/*    -- the argument descriptor array.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Given a vector of string-valued arguments such as that passed to main
**    and a vector describing the possible arguments, parseargs matches
**    actual arguments to possible arguments, converts values to the
**    desired type, and diagnoses problems such as missing arguments, extra
**    arguments, and argument values that are syntactically incorrect.
**
** ^REQUIREMENTS:
**    <argv> must be non-NULL and have a NULL pointer as its last item.
**
** ^SIDE-EFFECTS:
**    <argd> is modified accordingly as arguments are matched.
**
** ^RETURN-VALUE:
**    pe_SYSTEM
**       -- If a system error occurred
** 
**    pe_SUCCESS
**       -- success, no errors encountered.
** 
**    pe_SYNTAX
**       -- if a syntax error occurs
**
** ^ALGORITHM:
**    - call parse init
**    - if syntax-error, print usage and exit
**    - return
***^^**********************************************************************/
#ifdef __ANSI_C__
   int parseargs( char *argv[], ARGDESC argd[] )
#endif
{
   register ARGDESC *cmd;
   register char **av = argv;
   int rc = pe_SUCCESS;
   argMask_t  saveflags;

      /* allow null argument descriptor */
   if ( !argd )  argd = Empty_ArgDesc;

      /* initialize command-structure */
   if ( !CMD_isINIT(argd) )  init_args( argd );
   cmd = argd;
   saveflags = cmd_flags(cmd);

   if ( argv  &&  !BTEST(pa_ARGV0, cmd_flags(cmd)) ) {
      cmd_argv0(cmd) = basename( *av++ );
   }/*if*/

   rc = parse_argv_style( av, parse_init( &argd ) );

      /* scan for missing required arguments */
   if ( SYNTAX_ERROR(rc, argd) ) {
      fputc( '\n', stderr );
      usage( argd );
      exit( exit_SYNTAX );
   }

      /* reset previous parse-flags */
   cmd_flags(cmd) = saveflags;

   return  rc;
}
