/*************************************************************************
** ^FILE: vms_args.c - parse VMS/DCL argument vectors
**
** ^DESCRIPTION:
**    This file contains the routines used to parse VMS/DCL argument
**    vectors and to print VMS/DCL usage messages.
**
** ^HISTORY:
**    11/21/91	Brad Appleton	<brad@ssd.csd.harris.com>
**    - added Mike Levins fix to is_cmdline() to check for 0 length
**      returned by lib$get_foreign.
**    - added check of ps_NOTCMDLINE state-flag before calling is_cmdline()
**    - fixed problem in vms_parse() where ARGVALGIVEN was getting set in
**      place of ARGGIVEN.
**
**    08/27/91 	Earl Chew 	<cechew@bruce.cs.monash.edu.au>
**    - Use ProgNameLen when accessing ProgName
**    - Use get_argdesc() to access description
**
**    12/03/90	Brad Appleton	<brad@ssd.csd.harris.com>	Created
***^^**********************************************************************/

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

#ifdef vms
# include <descrip.h>
#endif

#include "strfuncs.h"
#include "pgopen.h"
#include "exit_codes.h"

#define PARSEARGS_PRIVATE   /* include private definitions */
#include "parseargs.h"

EXTERN  VOID  syserr       ARGS((const char *, ...));
EXTERN  VOID  usrerr       ARGS((const char *, ...));
EXTERN  VOID  get_winsize  ARGS((int, int *, int *));
EXTERN  BOOL  argInput     ARGS((ARGDESC *, char *, BOOL));
EXTERN  BOOL  argOutput    ARGS((ARGDESC *, char *, BOOL));

VERSIONID("$Header: vms_args.c,v 1.1 90/08/23 18:00:00 brad Exp $");

/***************************************************************************
** ^GLOBAL-VARIABLE: Usage_Requested
**
** ^VISIBILITY:
**    static-global (visible to all functions in this file).
**
** ^DESCRIPTION:
**    Indicates whether a usage message was requested by the user
**    (as opposed to triggerred by a syntax error).  If the message
**    is requested by the user then it is always printed in verbose
**    mode and does not return an error-status-code.
***^^**********************************************************************/
static  BOOL  Usage_Requested = FALSE;


#define MAXCMDLINE 255
#define VNULL (VOID *) 0

#define TOGGLE(flag)  flag = (flag) ? FALSE : TRUE

   /* define mappings */
#define  c_SPACE   '\001'   /* whitespace */
#define  c_QUAL    '\002'   /* qualifier-delimiter */
#define  c_EQUAL   '\003'   /* qualifier-argument separator */
#define  c_COLON   '\004'   /* qualifier-argument separator */
#define  c_PLUS    '\005'   /* list-item separator character */
#define  c_COMMA   '\006'   /* list-item separator character */


typedef enum {
   Parameter,   /* token is a parameter */
   Qualifier,   /* token is a qualifier */
   EndOfLine    /* NUL-token (signifies end of tokens) */
} dcl_arg_t;


typedef struct {
   dcl_arg_t  type;   /* token type */
   char      *token;  /* token value */
} dcl_token_t;



/***************************************************************************
** ^FUNCTION: is_cmdline - retrieve the original command-line
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static BOOL is_cmdline( argv, result )
/*
** ^PARAMETERS:
*/
   char *argv[];
/*    -- array of strings
*/
   char **result;
/*    -- pointer to resultant command-line
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Is_cmdline will compare the given vector of strings to the actual
**    string given on the command-line. If the two are approximately 
**    equivalent (modulo quotes and case) then the original command-line
**    is copied to result, otherwise all the elements of argv are concat-
**    enated together (in order and separated by whitespace) and assigned
**    to *result.
**
** ^REQUIREMENTS:
**    argv must be non-null
**
** ^SIDE-EFFECTS:
**    *result is assigned to either the concatenated argv string or the
**    original command-line. The result should be freed using free().
**
** ^RETURN-VALUE:
**    FALSE if the argv given is different from the command-line;
**    TRUE otherwise.
**
** ^CAVEATS:
**    The comparison is case blind and double quotes are ignored in the
**    command-line.  This is because lib$get_foreign returns double quotes
**    intact, while VAX-C strips them off.
**
** ^ACKNOWLEDGEMENTS:
**    Thanx to Jim Barbour for writing most of this code. --BDA
**
** ^ALGORITHM:
**    - Make a single string out of argv
**    - compare the "big" string to the command-line
**    - IF they are "equivalent" assign command-line to result & return TRUE.
**      ELSE assign the "big" string to result and return FALSE.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static BOOL is_cmdline( const char *argv[], char **result )
#endif
{
   register CONST char *avstr;
#ifdef vms
   unsigned long int stat;
   unsigned short int len;
   register CONST char *aptr, *sptr;
   static char str[ MAXCMDLINE ];
   static BOOL got_cmd_line = FALSE;
   $DESCRIPTOR(str_d, str);
#endif

      /* make a single string out of argv */
   avstr = strjoin( argv, " " );

#ifndef vms
   *result = (char *)avstr;
   return  FALSE;

#else
      /* get the original command-line */
   if ( ! got_cmd_line ) {
      stat = lib$get_foreign( &str_d, VNULL, &len, VNULL );
      str[len] = '\0';
      got_cmd_line = TRUE;
      if (! (stat & 1))  exit( stat );
   }

      /* if we didnt have a command-line, dont bother comparing */
   if ( !*str ) {
      *result = (char *)avstr;
      return  FALSE;
   }

      /* compare the two */
   for ( aptr = avstr, sptr = str ; *aptr && *sptr ; sptr++ ) {
      if ( toupper(*sptr) == toupper(*aptr) ) {
         ++aptr;
      }
      else  if ( *sptr != '"' ) {
         *result = (char *)avstr;
         return  FALSE;
      }
   }

   *result = strdup( str );
   free( avstr );
   return  TRUE;
#endif
}


/***************************************************************************
** ^FUNCTION: dcl_strxlat - translate a string according to DCL syntax
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static char *dcl_strxlat( str )
/*
** ^PARAMETERS:
*/
   char *str;
/*    -- the string to translate.
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Dcl_strxlat will attempt to convert the given string to canonical
**    form by escaping any unquoted special characters, and removing any
**    unquoted whitespace around special characters (such as '=' and '/').
**    Since the special characters are replaced with special codes, quotes
**    are also removed.
**
** ^REQUIREMENTS:
**    <str> should be non-null and non-empty
**
** ^SIDE-EFFECTS:
**    <str> is "trimmed" to canonical form and special characters are mapped
**    to a unique code.
**
** ^RETURN-VALUE:
**    The address of the translated string.
**
** ^ALGORITHM:
**    - remove all unquoted whitespace following any unquoted "/:=+("
**    - remove all unquoted whitespace preceding any unquoted "/:=+)"
**    - compress all unquoted whitespace,
**    - remove all unquoted parentheses,
**    - re-map all other unquoted special characters and remove quotes.
**      use the following mapping:
**           whitespace   ==>   '\001'
**             '/'        ==>   '\002'
**             ':' & '='  ==>   '\003'
**             ',' & '+'  ==>   '\004'
***^^**********************************************************************/
#ifdef __ANSI_C__
   static char *dcl_strxlat( char *str )
#endif
{
   register char c, *pread = str, *pwrite = str;
   BOOL quoted = FALSE;

   /*
   ** pass1 - scan forward, removing all whitespace after unquoted "/:=+,("
   */
   while ( c = *pwrite++ = *pread++ ) {
      if ( c == '"' )   TOGGLE(quoted);
      if ( !quoted   &&   strchr("/:=+,(", c) )
         while( isspace(*pread) )   ++pread;
   }
   *--pwrite = '\0';   /* NUL terminate */

   /*
   ** pass2 - scan backward, removing all whitespace before unquoted "/:=+,)"
   */
   pread = --pwrite;  /* set to last NON-NUL char */
   quoted = FALSE;
   while ( pread >= str ) {
      c = *pwrite-- = *pread--;
      if ( c == '"' )   TOGGLE(quoted);
      if ( !quoted   &&   strchr("/:=+,)", c) )
         while( isspace(*pread) )   --pread;
   }
   strcpy(str, ++pwrite);   /* reset BOS */

   /*
   ** pass3 - compress all unquoted whitespace,
   **         remove all unquoted parentheses,
   **         re-map all other unquoted special characters and remove quotes.
   **           use the following mapping:
   **    whitespace   ->   '\001'
   **      '/'        ->   '\002'
   **      ':' & '='  ->   '\003'
   **      ',' & '+'  ->   '\004'
   */
   pread = pwrite = str;
   quoted = FALSE;
   while ( c = *pread++ ) {
      if ( c == '"' )
         TOGGLE(quoted);
      else if ( !quoted   &&   isspace(c) ) {
         *pwrite++ = c_SPACE;
         while( isspace(*pread) )   ++pread;
      }
      else if ( !quoted   &&   (c == '(' || c == ')') )
         continue;
      else if ( !quoted   &&   c == '/' )
         *pwrite++ = c_QUAL;
      else if ( !quoted   &&   c == ':' )
         *pwrite++ = c_COLON;
      else if ( !quoted   &&   c == '=' )
         *pwrite++ = c_EQUAL;
      else if ( !quoted   &&   c == '+' )
         *pwrite++ = c_PLUS;
      else if ( !quoted   &&   c == ',' )
         *pwrite++ = c_COMMA;
      else
         *pwrite++ = c;
   }/*while*/

   *pwrite = '\0';   /* NUL-terminate */
   return   str;
}


/***************************************************************************
** ^FUNCTION: dcl_split - split a string up into a vector of DCL tokens
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static dcl_token_t  *dcl_split( str )
/*
** ^PARAMETERS:
*/
   char *str;
/*    -- the string to split up into tokens
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Dcl_split will split a string up into tokens (according to DCL grammar
**    rules) and will additionally associate each token with a type (namely:
**    a qualifier, a positional paramater, or the End-of-Tokens symbol).
**
** ^REQUIREMENTS:
**    Assume dcl_strxlat(str) has already been performed.
**
** ^SIDE-EFFECTS:
**    <str> is modified in much the same manner as it would have 
**    been modified if it were passed as the vector_string to strsplit().
**
** ^RETURN-VALUE:
**    A vector of dcl_tokens.
**
** ^ALGORITHM:
**    - first count the number of tokens and also try to interpret stuff
**      like  "parm1.1/qual1,parm1.2" by replacing the comma with a space.
**    - allocate space for the vector of DCL tokens.
**    - assign the approriate value and type for each token.
**
** ^CAVEATS:
**    Does not treate "/qual=(val1,val2/str,..)" as illegal
**        ( parses it as if it were "/qual=(val1,val2)/str" )
**
**    Replaces "parm1.1/qual,parm1.2" with "parm1.1/qual parm1.2"
**        which works only because parseargs requires a VMS
**        positional list to be comma OR whitespace separated
**        (not just comma separated).
***^^**********************************************************************/
#ifdef __ANSI_C__
   static dcl_token_t  *dcl_split( char *str )
#endif
{
   int tokc = 1;  /* number of tokens */
   dcl_token_t *tokv = (dcl_token_t *)NULL;  /* vector of tokens */
   register char *pread, c;
   register int i;

   if ( !str  ||  !(*str) )  return  (dcl_token_t *)NULL;

	/* 1st pass (left-to-right) : count tokens */
   pread = ( *str == c_QUAL ) ? (str + 1) : str;
   while ( c = *pread++ ) {
      if ( c == c_QUAL  ||  c == c_SPACE )  ++tokc;
      if ( c == c_QUAL ) {
	       /* replace "p1.1/qual,p1.2" with "p1.1/qual p1.2" */
        char *p, delims[5];

        sprintf( delims, "%c%c%c%c", c_EQUAL, c_PLUS, c_QUAL, c_SPACE );
        if ( (p = strpbrk((str + 1), delims))  &&
             ((*p == c_PLUS) || (*p == c_COMMA)) )
           *p == c_SPACE;
      }
   }


	/* allocate vector */
   tokv = (dcl_token_t *)malloc( (tokc + 1) * sizeof(dcl_token_t) );
   if ( tokv == (dcl_token_t *)NULL ) {
      syserr( "malloc() failed in dcl_split()" );
   }
   tokv[ tokc ].type = EndOfLine;
   tokv[ tokc ].token = CHARNULL;

	/* 2nd pass (right-to-left) : assign tokens to strings */
   for ( i = 1, --pread ; pread >= str ; pread-- ) {
      if ( *pread == c_SPACE  ||  *pread == c_QUAL ) {
         tokv[ tokc - i ].token = pread + 1;
         tokv[ tokc - i ].type = ( *pread == c_QUAL ) ? Qualifier : Parameter;
         *pread = '\0';
         ++i;
      }
   }

   if ( *str ) {  /* then 1st char could NOT have been '/' */
     tokv -> token = str;
     tokv -> type  = Parameter;
   }

   return  tokv;
}


/***************************************************************************
** ^FUNCTION: dcl_restore - restore the `escaped' characters in a token
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static char *dcl_restore( tokstr )
/*
** ^PARAMETERS:
*/
   char *tokstr;
/*    -- the token string to restore
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Dcl_restore will attempt to restore any DCL special characters (such as
**    '/' and '=') that may have been escaped by dcl_strxlat().
**
** ^REQUIREMENTS:
**    tokstr should be non-null and non-empty
**
** ^SIDE-EFFECTS:
**    Any escape characters (such as c_QUAL) are restored to their ascii
**    representation.
**
** ^RETURN-VALUE:
**    The address of the restored string
**
** ^ALGORITHM:
**    - for each character in tokstr
**      - if it is special then replace it with its ascii code
**        end-if
**      end-for
**
** ^CAVEATS:
**    The string is not restored to way it was before it was processed by
**    dcl_strxlat(). Any characters that were removed are still missing.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static char *dcl_restore( char *tokstr )
#endif
{
   register  char *str = tokstr;

   if ( !str || !*str )  return  str;

   for ( ; *str ; str++ ) {
      switch( *str ) {
         case c_SPACE  : *str = ' '; break;
         case c_QUAL   : *str = '/'; break;
         case c_EQUAL  : *str = '='; break;
         case c_COLON  : *str = ':'; break;
         case c_PLUS   : *str = '+'; break;
         case c_COMMA  : *str = ','; break;
         default : break;
      }
   }

   return  tokstr;
}




/***************************************************************************
** ^FUNCTION: split_list - function to handle ARGLISTs and ARGVECs
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static BOOL split_list( ad, vp, cmd )
/*
** ^PARAMETERS:
*/
   ARGDESC *ad;
/*    -- the argument which takes multiple values
*/
   char *vp;
/*    -- the string of values for the argument
*/
   ARGDESC *cmd;
/*    -- the command to which the argument belongs
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Split_list will split the string containing the set of values into
**    a set of tokens and will then attempt to convert each token (in the
**    order given) using the ad_type function of the argument structure.
**
** ^REQUIREMENTS:
**    <vp> must already be preprocessed by dcl_strxlat to escape any quoted
**    characters and to map special characters to their corresponding values.
**
** ^SIDE-EFFECTS:
**    Ad has some of its flags modified as well as any modifications that
**    are made by the ad_type function.
**
**    <vp> is modified by strsplit().
**
** ^RETURN-VALUE:
**    TRUE if all is hunky-dory; FALSE otherwise
**
** ^ALGORITHM:
**    - Split vp into a vector of tokens
**    - foreach token
**      - call ad_type(ad, token, copyf)
**      end-if
**    - set the ARGGIVEN and ARGVALGIVEN flags accordingly
***^^**********************************************************************/
#ifdef __ANSI_C__
   static BOOL  split_list( ARGDESC *ad, char *vp, ARGDESC *cmd )
#endif
{
   char **arg_vec = (char **)NULL;
   int  i, arg_num = 0;
   BOOL err = FALSE;
   char delims[3];

      /* set-up delimiter string */
   *delims = c_PLUS;
   *(delims + 1) = c_COMMA;
   *(delims + 2) = '\0';

      /* break string up into to tokens and handle each one */
   arg_num = strsplit( &arg_vec, vp, delims );
   for ( i = 0 ; i < arg_num ; i++ ) {
      vp = arg_vec[i];

         /* try to convert the type */
      if ( !HANDLE(ad, dcl_restore(vp), cmd_flags(cmd)) )  err = TRUE;
   }
   if ( !err )  BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );

   if ( ARG_isPOSITIONAL(ad) ) {
      cmd_list(cmd) = ad;
   }
   else {
      cmd_list(cmd) = ARGDESCNULL;
   }

   free( arg_vec );

   return  !err;
}


/***************************************************************************
** ^FUNCTION: vms_parse - parse VMS/DCL arg-vectors
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int vms_parse( argv, argd )
/*
** ^PARAMETERS:
*/
   char *argv[];
/*    -- the vector of string arguments from the command-line
*/
   ARGDESC argd[];
/*    -- the programmer description of the command and its args
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Vms_parse will parse the arguments in the given vector of strings,
**    assign the corresponding values to the command-line arguments specified
**    in argd, and check the syntax of the command-line.
**
** ^REQUIREMENTS:
**    The final element in argv must be a NULL pointer.
**
** ^SIDE-EFFECTS:
**    argd is modified according to the command-line description and parameters
**
** ^RETURN-VALUE:
**    pe_SUCCESS (0) if no errors are encountered
**    pe_SYSTEM (-1) if a system error is encountered
**    pe_SYNTAX if a syntax error is encountered
**
** ^ALGORITHM:
**    - compare argv to the command-line (use the command-line if equal)
**    - put argv back into a single string and translate it using dcl_strxlat
**    - reparse the string into DCL tokens using dcl_strsplit
**    - for each DCL token
**       - attempt to match the token as a qualifier
**       - if it is a qualifier
**          - record and convert its value (if any)
**       - else it is a positional parameter
**          - record and convert its value (if any)
**       - else there are too many arguments
**          - return pe_SYNTAX
**         end-if
**       end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
   int  vms_parse( char *argv[], ARGDESC argd[] )
#endif
{
   register ARGDESC *ad, *args, *cmd;
   register char *p;
   char *avstr;
   BOOL  is_match = FALSE;
   int  parse_error = pe_SUCCESS;
   dcl_token_t  *tok, *tokvec;
   argName_t  keyword;
   argMask_t  saveflags, flags;

   if ( !argd )  return  parse_error;

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

   if ( !argv || !*argv )  return  parse_error;

   if ( !BTEST(cmd_state(cmd), ps_NOTCMDLINE) ) {
      (VOID) is_cmdline( (CONST char **)argv, &avstr );
   }
   else {
      avstr = strjoin( argv, " " );
   }

   BSET( cmd_flags(cmd), pa_COPYF );
   (VOID) dcl_strxlat( avstr );
   if ( !avstr || !*avstr )  return  parse_error;
   tokvec = dcl_split( avstr );

      /* run through the token vector */
   for ( tok = tokvec ; (p = tok -> token) ; tok++ ) {

      if ( tok -> type == Qualifier  &&  !BTEST(cmd_state(cmd), ps_NOFLAGS) ) {
         char c = '\0', *s, delims[3];

            /* set-up delimiter string */
         *delims = c_EQUAL;
         *(delims + 1) = c_COLON;
         *(delims + 2) = '\0';

            /* skip past qualifier prefix and look for possible argument */
         s  = strpbrk(p, delims);
         if (s) {
            c = *s;
            *s++ = '\0';
         }

         is_match = FALSE;
         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 (!ARG_isPOSONLY(ad)  &&  match(p, arg_sname(ad)) == 0) {
                  is_match = TRUE;
                  break;
               }/*if*/
            }
         }

         if (c)  *(s-1) = c;  /* restore the equal sign */

         if ( !is_match ) {
            if (s)  *(s-1) = '\0';
            usrerr( "undefined qualifier %s", s_KWD_PFX, p );
            if (s)  *(s-1) = c;
            parse_error = pe_SYNTAX;
            continue;
         }

            /* end-qualifiers */
         if ( arg_type(ad) == argEnd ) {
            BSET( cmd_state(cmd), ps_NOFLAGS );
            continue;
         }
            /* if usage - just print usage and exit */
         if ( arg_type(ad) == argUsage ) {
            Usage_Requested = TRUE;
            usage( argd );
            free( avstr );
            if ( tokvec )  free( tokvec );
            cmd_flags(cmd) = saveflags;
            exit(exit_USAGE);
         }
         /* reset the argument flags - if this arg was already given, some
         ** of its flags may be set to indicate how it was given before.
         ** we need to know how it was given now (but save the old ones
         ** just in case the new one fails).
         */
         flags = arg_flags(ad);
         if ( ARG_isGIVEN(ad) ) {
            BCLEAR( arg_flags(ad), ARGVALSEP );
            if ( !ARG_isMULTIVAL(ad) )  BCLEAR( arg_flags(ad), ARGVALGIVEN );
         }

            /* ARGNOVALs are special, having no value */
         if ( ! ARG_isVALTAKEN(ad) ) {
            if ( !HANDLE(ad, dcl_restore(s), cmd_flags(cmd)) ) {
               arg_flags(ad) = flags;
               parse_error = pe_SYNTAX;
            }
            else {
               BSET( arg_flags(ad), ARGGIVEN );
               ad = ARGDESCNULL;
            }
            continue;
         }/*if ARGNOVAL*/

            /* now get the real value */
         if ( !s || !(*s) ) {
            if ( ARG_isVALOPTIONAL(ad) ) {
               BSET( arg_flags(ad), ARGGIVEN );
            }
            else {
               (VOID) get_kwdname( arg_sname(ad), keyword );
               usrerr("qualifier %s requires an argument", keyword);
               arg_flags(ad) = flags;
               parse_error = pe_SYNTAX;
            }
            continue;
         }/*if*/

         if( ARG_isMULTIVAL(ad) ) {
            if( !split_list(ad, s, cmd) ) {
               arg_flags(ad) = flags;
               parse_error = pe_SYNTAX;
            }
            else {
               BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
            }
            continue;
         }/*if list*/

            /* try to convert the type */
         if ( !HANDLE(ad, dcl_restore(s), cmd_flags(cmd)) ) {
            arg_flags(ad) = flags;
            parse_error = pe_SYNTAX;
         }
         else {
            BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
         }

         continue;
      }/*if qual*/
      else {
            /* parsing a vector of arguments */
         if ( cmd_list(cmd) ) {
            ad = cmd_list(cmd);
            flags = arg_flags(ad);
            if ( ARG_isGIVEN(ad) ) {
               BCLEAR( arg_flags(ad), ARGVALSEP );
            }

            BSET( arg_flags(ad), ARGVALSEP );

            if( ARG_isMULTIVAL(ad) ) {
               if( !split_list(ad, p, cmd) )  parse_error = pe_SYNTAX;
            }
            else if ( !HANDLE(ad, dcl_restore(p), cmd_flags(cmd)) ) {
               arg_flags(ad) = flags;
               parse_error = pe_SYNTAX;
            }

            if ( !parse_error )  BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );

            continue;
         }
            /* positional argument */
         is_match = FALSE;
         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 ( ARG_isPOSITIONAL(ad)  &&
                    (!ARG_isGIVEN(ad) || ARG_isMULTIVAL(ad)) ) {
                  is_match = TRUE;
                  break;
               }/*if*/
            }
         }

         if ( !is_match ) {
            usrerr("too many arguments");
            parse_error = pe_SYNTAX;
            continue;
         }

         /* if FLAGS1ST is set then first positional marks end-of-flags */
         if ( BTEST(cmd_flags(cmd), pa_FLAGS1ST) ) {
            BSET( cmd_state(cmd), ps_NOFLAGS );
         }

         /* reset the argument flags - if this arg was already given, some
         ** of its flags may be set to indicate how it was given before.
         ** we need to know how it was given now (but save the old ones
         ** just in case the new one fails).
         */
         flags = arg_flags(ad);
         if ( ARG_isGIVEN(ad) ) {
            BCLEAR( arg_flags(ad), ARGVALSEP );
            if ( !ARG_isMULTIVAL(ad) )  BCLEAR( arg_flags(ad), ARGVALGIVEN );
         }

         BSET( arg_flags(ad), ARGVALSEP );

         if( ARG_isMULTIVAL(ad) ) {
            if( !split_list(ad, p, cmd) ) {
               arg_flags(ad) = flags;
               parse_error = pe_SYNTAX;
            }
            else {
               BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
            }
            continue;
         }/*if list*/

            /* try to convert */
         if ( !HANDLE(ad, dcl_restore(p), cmd_flags(cmd)) ) {
            arg_flags(ad) = flags;
            parse_error = TRUE;
         }
         else {
            BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
         }

      }/*if parameter*/
   }/*while*/

   free( avstr );
   if ( tokvec )  free( tokvec );
   cmd_flags(cmd) = saveflags;
   return  parse_error;
}


/***************************************************************************
** ^FUNCTION: fmtarg - format command-argument syntax
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static int fmtarg( ad, buf )
/*
** ^PARAMETERS:
*/
   ARGDESC *ad;
/*    -- pointer to the argument to format
*/
   char *buf;
/*    -- character buffer to hold the formatted result
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Fmtarg will determine the proper command-line syntax for the
**    given argument and write the result to the given buffer.
**
** ^REQUIREMENTS:
**    buf must be large enough to hold the formatted result (100 characters
**    should do the trick).
**
** ^SIDE-EFFECTS:
**    buf is overwritten.
**
** ^RETURN-VALUE:
**    The number of printable characters in the argument-syntax-string
**
** ^ALGORITHM:
**    Print argument usage based on whether or not the argument is
**    positional, hidden, multi-valued (list or vector), etc ....
**    Optional arguments and values are enclosed in square braces.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static int fmtarg( const ARGDESC *ad, char *buf )
#endif
{
   /* buf must already be large enough */
   char * pos;
   argName_t  keyword, name;

   (VOID) get_argname( arg_sname(ad), name );

   if (ARG_isPOSITIONAL(ad)) {
      sprintf( buf, "<%s>", name );
   }
   else {
      (VOID) get_kwdname( arg_sname(ad), keyword );
      sprintf( buf, "%c%s", *s_KWD_PFX, keyword );
      pos = buf + strlen(buf);

      if ( ARG_isVALTAKEN(ad) && !ARG_isBOOLEAN(ad) && !ARG_isPSEUDOARG(ad) ) {
         if  ( ARG_isVALOPTIONAL(ad)) {
            sprintf( pos, "[%c<%s>]", *s_ARG_SEP, name );
         }
         else {
            sprintf( pos, "%c<%s>", *s_ARG_SEP, name );
         }
      }/*if*/
   }/*else*/

   return  (int) strlen(buf);
}


/***************************************************************************
** ^FUNCTION: vms_usage - print a usage message
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   VOID vms_usage( argd, usage_flags )
/*
** ^PARAMETERS:
*/
   ARGDESC *argd;
/*    -- the command-descriptor array
*/
   argMask_t usage_flags;
/*    -- flags set by $USAGECNTL
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Vms_usage will print the VMS/DCL command-line usage of the given
**    command on standard diagnostic output (stderr). The content of the
**    usage message is controlled by the bitmasks in usage_flags which
**    correspond to the settings in the user's USAGECNTL symbol.
**
** ^REQUIREMENTS:
**    argd should be a non-null command-line argument-descriptor array
**
** ^SIDE-EFFECTS:
**    Prints on stderr.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - if no usage is desired then exit
**    - if paging is requested print to the pager instead of stderr
**    - print the command-line syntax
**    - if the description is requested print it
**    - if verbose mode is requested, print the description of each argument
***^^**********************************************************************/
#ifdef __ANSI_C__
   void vms_usage( const ARGDESC *argd, argMask_t usage_flags )
#endif
{
   register CONST ARGDESC  *ad, *args, *cmd;
   int  max_cols = 80, max_lines  = 24;
   int  margin, ll, pl, qualifiers, longest, positionals;
   BOOL first = TRUE;
   FILE *fp;

   if ( !argd )  return;

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

      /* get screen size */
   get_winsize( fileno(stderr), &max_lines, &max_cols );

      /* force verbose-mode if requested */
   if ( Usage_Requested )   BSET( usage_flags, usg_VERBOSE );

   if ( BTEST(usage_flags, usg_NONE) )  return;

   fp = ( BTEST(usage_flags, usg_PAGED) )
      ? pgopen( stderr, getenv("USAGE_PAGER") )
      : stderr;

      /* allow null argument descriptor */
   fprintf(fp, "Format: %.*s", ProgNameLen, (ProgName) ? ProgName : "");

   ll = ProgNameLen + 8;
   margin = ll + 1;
   longest = 0;

   for ( positionals = 0 ; positionals < 2 ; positionals++ ) {
      for ( args = argd ; args ; args = cmd_defargs(args) ) {
         for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
            argName_t  buf, name;

               /* don't display hidden arguments */
            if ( ARG_isHIDDEN(ad) )  continue;
            if ( !positionals  &&  ARG_isPOSITIONAL(ad) )  continue;
            if ( positionals  &&  !ARG_isPOSITIONAL(ad) )  continue;

               /* figure out how wide this parameter is (for printing) */
            pl = fmtarg(ad, buf);

            if ( pl > longest )  longest = pl;


            if ( ARG_isMULTIVAL(ad) ) {
               (VOID) get_argname( arg_sname(ad), name );
               strcat(buf, "[,<");
               strcat(buf, name);
               strcat(buf, ">...]");
               pl += 8 + strlen(name);
            }
            if ( !ARG_isREQUIRED(ad) ) {
               pl += 2;  /* [] */
            }

            /* see if this will fit */
            if ( (ll + pl + 1) > (max_cols - first) ) {
                  /* no... start a new line */
               fprintf(fp, "\n%*s", margin, "");
               ll = margin;
            }
            else {
                  /* yes... just throw in a space */
               fputc(' ', fp);
               ++ll;
            }
            ll += pl;

                /* show the argument */
            if ( !ARG_isREQUIRED(ad) )  fputc('[', fp);
            fprintf(fp, buf);
            if ( !ARG_isREQUIRED(ad) )  fputc(']', fp);

            first = FALSE;  /* not first line anymore */
         }/*for each ad */
      }/* for each argd */
   }/* for each parm-type */

   fputc('\n', fp);

   if ( BTEST(usage_flags, usg_DESCRIPTION) ) {
      CONST char *description = cmd_description(cmd);

      if ( description  &&  *description ) {
         fprintf( fp, "Description:\n" );
         indent_para(fp, max_cols, 8, "", 0, description, 0);
         fputc( '\n', fp );
      }
   }/*if*/

   if ( !BTEST(usage_flags, usg_VERBOSE) )  {
      if ( pgactive(fp) )  (VOID) pgclose( fp );
      return;
   }

   qualifiers = 0;
   for ( positionals = 0 ; positionals < 2 ; positionals++ ) {
      for ( args = argd ; args ; args = cmd_defargs(args) ) {
         for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
            argName_t  buf;
	    char  *desc;
	    int  desclen;

               /* don't display hidden arguments */
            if ( ARG_isHIDDEN(ad) )  continue;
            if ( !positionals  &&  ARG_isPOSITIONAL(ad) )  continue;
            if ( positionals  &&  !ARG_isPOSITIONAL(ad) )  continue;

            if ( !qualifiers++ )  fprintf(fp, "Qualifiers/Parameters:\n");
            (VOID) fmtarg(ad, buf);
	    desc = get_argdesc(arg_description(ad), &desclen);
            indent_para(fp, max_cols, 8, buf, longest+2, desc, desclen );
         }/*for each ad */
      }/* for each argd */
   }/* for each parm-type */

   if ( pgactive(fp) )  (VOID) pgclose( fp );
}

