/*************************************************************************
** ^FILE: argtype.c - argument type definitions for parseargs(3)
**
** ^DESCRIPTION:
**    This file implements the argument conversion functions for
**    converting string, character, integer, floating-point,
**    boolean, and pseudo-arguments, from command-line strings.
**
** ^HISTORY:
**    01/03/91	Brad Appleton	<brad@ssd.csd.harris.com>
**       - Added structured block comments
**       - Added argUsage & argDummy dummy-functions
**       - Added argSBool, argTBool, and argUBool
**       - Added ARGVEC handling to all necessary functions
**       - Added argInput & argOutput for VMS
**       - put floating-point routines (argFloat & argDouble) into
**         this file (they may be excluded by #defining NOFLOAT)
**       - changed routines to return negative values (where appropriate)
**
**    --/--/--	Peter da Silva	<peter@ferranti.com>
**
**    --/--/--	Eric P. Allman	<eric@Berkeley.EDU> 	Created
***^^**********************************************************************/

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

#define PARSEARGS_NARGTYPES  /* exclude arg-type externs */
#include "parseargs.h"

#ifdef __ANSI_C__
# define  PARMS(ad,vp,copyf) \
	( register ARGDESC *ad,  register char *vp,  BOOL copyf )
#else
# define  PARMS(ad,vp,copyf) \
	( ad, vp, copyf )  register ARGDESC *ad;  register char *vp;  BOOL copyf;
#endif

#define REALLOC(ptr,size)  (( ! ptr ) ? malloc(size) : realloc(ptr, size) )
EXTERN  VOID    syserr  ARGS((const char *, ...));
EXTERN  VOID    usrerr  ARGS((const char *, ...));

#ifndef __ANSI_C__
   EXTERN  long    strtol  ARGS((char *, char **, int));
   EXTERN  double  strtod  ARGS((const char *, char **));
#endif

/***************************************************************************
** ^FUNCTION: argtype -- argument translation routines.
**
** ^SYNOPSIS:
**    BOOL  argUsage( ad, vp, copyf )
**    BOOL  argEnd( ad, vp, copyf );
**    BOOL  argDummy( ad, vp, copyf );
**    BOOL  argBool( ad, vp, copyf );
**    BOOL  argSBool( ad, vp, copyf );
**    BOOL  argUBool( ad, vp, copyf );
**    BOOL  argTBool( ad, vp, copyf );
**    BOOL  argChar( ad, vp, copyf );
**    BOOL  argStr( ad, vp, copyf );
**    BOOL  argInt( ad, vp, copyf );
**    BOOL  argShort( ad, vp, copyf );
**    BOOL  argLong( ad, vp, copyf );
**    BOOL  argFloat( ad, vp, copyf );
**    BOOL  argDouble( ad, vp, copyf );
**    BOOL  argInput( ad, vp, copyf );
**    BOOL  argOutput( ad, vp, copyf );
**
** ^PARAMETERS:
**    ARGDESC *ad;
**    -- the argument descriptor for this parameter.
**
**    char *vp;
**    -- a pointer to the string input value.
**
**    BOOL  copyf;
**    -- if TRUE, the value will be destroyed later, and so should be copied
**       if it will be retained (as for a string).
**
** ^DESCRIPTION:
**    Each of these converts a parameter value to the internal form, includ-
**    ing validity checking.  Their parameters and return values all behave
**    similarly. One of these routines is called when an argument of that
**    particular type is matched by one of the argument parsing function in
**    parseargs(3). When such an argument is matched, its argument transla-
**    tion routine is invoked and is passed (1) the address of the argument
**    descriptor for the matched argument, (2) the possible argument string
**    for that matched argument, and (3) a boolean field that is TRUE only
**    if the second parameter points to temporary storage (indicating that
**    some copying may need to be done instead of just pointing to the same
**    object).
**
**    Once the argument translation routine is invoked, it is responsible
**    for converting the argument string to the desired internal form
**    (perhaps a number), and assigning the resultant value to the
**    arg_valp(ad) field of the argument descriptor (this includes handling
**    any necessary (re)allocation if the matched argument has the ARGVEC
**    flag enabled). If the argument is an ARGVEC or ARGLIST then the rou-
**    tine is responsible for allocating any space, copying the arg-flags to
**    the value-specific flags, and setting the ARGCOPYF flag for the value
**    if it needs to be allocated as well.
**
** ^REQUIREMENTS:
**    ARGKEYWORD should be set if the argument was matched via its
**    string name (as opposed to by its character name).
**
**    ARGVALSEP should be set is the argument value was in a separate
**    argv element from the argument string-name (or character name).
**
** ^SIDE-EFFECTS:
**    The value used should be stored in the location indicated by arg_valp(ad).
**
** ^RETURN-VALUE:
**    TRUE : if the conversion was successful and the entire value was used.
**
**    FALSE : if the conversion failed.  The reason for failure should be
**            diagnosed using usrerr().
**
**    -N : if the conversion was successful but only N characters of the value
**         were used, the remaining characters may still match other arguments.
** 
** ^ALGORITHM:
**    Function-specific, but the basic idea is as follows:
**
**    - convert the value-string into the desired type
**    - if the value is invalid call usrerr and return FALSE
**      end-if
**    - if this ad is an ARGVEC
**        - expand the vector and insert the new item at the end
**        - update the item count
**    - else 
**        - set *ad_valp to the converted value
**      end-if
**    - return TRUE if we used the whole arg, -N if we only used N-characters
***^^**********************************************************************/

/* vector types and defines */
#define BLOCKSIZE  5    /* number of items to allocate at once */
#define VEC_SIZE(vec,el_typ)  ( sizeof(el_typ *) * (BLOCKSIZE + vec->count) )
typedef ARGVEC_T(char *)  strvec_t;
typedef ARGVEC_T(char)    charvec_t;
typedef ARGVEC_T(int)     intvec_t;
typedef ARGVEC_T(short)   shortvec_t;
typedef ARGVEC_T(long)    longvec_t;
typedef ARGVEC_T(float)   floatvec_t;
typedef ARGVEC_T(double)  doublevec_t;


/***************************************************************************
** ^SECTION: PSEUDO-TYPES -- argUsage, argEnd, argDummy
**    ArgUsage is used to specify an argument that causes the command
**    usage to be printed.
**
**    ArgDummy is used to force an item to show up in usage-messages but
**    the item itself is never matched against any arguments from the
**    command-line.
**
**    ArgEnd is used by amiga_args.c and vms_args.c to indicate an argument
**    that forces all remaining arguments to be considered positional args.
**
**    These three are dummy functions. The routines themselves do nothing
**    of importance, we just need to have their addresses available for
**    identification in the corresponding <os>_args.c file.
***^^**********************************************************************/

/*ARGSUSED*/
BOOL argDummy  PARMS(ad, vp, copyf)
{
   return   FALSE;
}


/*ARGSUSED*/
BOOL argEnd  PARMS(ad, vp, copyf)
{
   return (FALSE);
}


/*ARGSUSED*/
BOOL argUsage  PARMS(ad, vp, copyf)
{
   return   FALSE;
}


/***************************************************************************
** ^SECTION: STRING-TYPES -- argStr
**    ArgStr is one of the few argument translation routines that actually
**    uses the <copyf> flag. If <copyf> is true then the string is duplicated.
**
**    ArgStr assigns the given string (or a copy of it) to the value referenced
**    by arg_valp(unless the argument is a vector in which case the given string
**    is appended to the end).
**
**    ArgStr ensures that the very last item in a vector of strings (the one
**    accessed as vec.array[ vec.count ]) will always be NULL.
***^^**********************************************************************/

/*ARGSUSED*/
BOOL argStr  PARMS(ad, vp, copyf)
{
   char *cp;
   argName_t  argname;

   (VOID) get_argname( arg_sname(ad), argname );
   if (copyf) {
      register int i;

      i = strlen(vp) + 1;
      cp = (char *) malloc(i * sizeof(char));
      if (!cp) {
         usrerr("out of memory parsing %s", argname);
         return FALSE;
      }
      memcpy(cp, vp, i);
   }
   else
      cp = vp;

   if ( ARG_isVEC(ad) ) {
      strvec_t  *vec = (strvec_t *)arg_valp(ad);

      if ( (vec->count % BLOCKSIZE) == 0 ) {
         vec->array = (char **) REALLOC(vec->array, 1+VEC_SIZE(vec, char *));
         if ( !vec->array ) {
            if ( copyf )  free(cp);
            syserr("out of memory saving arg %s", argname);
         }
         vec->flags = (argMask_t *) REALLOC(vec->flags, VEC_SIZE(vec, argMask_t));
         if ( !vec->flags ) {
            syserr("out of memory saving arg %s", argname);
         }
      }

      vec->flags[ vec->count ] = arg_flags(ad);
      if ( copyf )  BSET( vec->flags[vec->count], ARGCOPYF );
      vec->array[ (vec->count)++ ] = cp;
      vec->array[ vec->count ] = (char *)NULL;
   }
   else
      *(char **) arg_valp(ad) = cp;

   return (TRUE);
}


/***************************************************************************
** ^SECTION: CHARACTER-TYPES -- argChar
**    ArgChar assigns the given character to the value referenced by ad_valp
**    (unless the argument is a vector in which case the given character
**    is appended to the end).
**
**    If an argChar argument is matched as a single character option, then
**    the immediately following character will be considered its argument
**    (but the characters after it may still be processed as option-letters).
**
**    ArgChar ensures that the very last item in a vector of character (the
**    one accessed as vec.array[ vec.count ]) will always be a NUL byte so
**    that the resulting vector may also be used as a NULL terminated string.
**
**    Unlike argStr, argChar will translate character escape sequences such
**    as '\n' and '\012'.
***^^**********************************************************************/

/*ARGSUSED*/
BOOL argChar  PARMS(ad, vp, copyf)
{
   auto char *vpp;
   argName_t  argname;
   int status = FALSE;
   char c;

   (VOID) get_argname( arg_sname(ad), argname );
   if (!vp || !*vp) {
      status = FALSE;
   }
   if (strlen(vp) == 2 && vp[0]=='^') {
      c = vp[1] ^ '@';
      status = TRUE;
   }
   else if (strlen(vp) > 1 && vp[0]=='\\') {
      c = (int) strtol(&vp[1], &vpp, 8);
      if (*vpp == '\0')
         status = TRUE;
   }
   else if (strlen(vp) == 1) {
      c = *vp;
      status = TRUE;
   }
   else if ( !BTEST(arg_flags(ad), ARGVALSEP | ARGKEYWORD) ) {
      c = *vp;
      status = TRUE;
   }

   if ( status ) {
      if ( ARG_isVEC(ad) ) {
         charvec_t  *vec = (charvec_t *)arg_valp(ad);

         if ( (vec->count % BLOCKSIZE) == 0 ) {
            vec->array = (char *) REALLOC(vec->array, 1+VEC_SIZE(vec, char));
            if (!vec->array)  syserr("out of memory saving arg %s", argname);
         }
         vec->flags = (argMask_t *) REALLOC(vec->flags, VEC_SIZE(vec, argMask_t));
         if ( !vec->flags ) {
            syserr("out of memory saving arg %s", argname);
         }

         vec->flags[ vec->count ] = arg_flags(ad);
         vec->array[ (vec->count)++ ] = c;
         vec->array[ vec->count ] = '\0';
      }
      else
         *(char *) arg_valp(ad) = c;
   }
   else {
      usrerr("invalid character argument '%s' for %s",
         vp, argname);
   }
   return (status) ? (BOOL) -1 : FALSE;
}


/***************************************************************************
** ^SECTION: INTEGER-TYPES -- argInt, argShort, argLong
**    Each of these functions converts the given string to the desired
**    integral type. The value may be specified as an octal number by
**    specifying the first digit to be 0. Similarly, If the first two 
**    characters are '0x' then the number is treated as hexadecimal.
***^^**********************************************************************/

   /*
   ** macro to define an integral argtype function
   **
   ** NOTE : do NOT use a terminating semicolon when invoking this macro!
   */
#define  INTEGRAL_ARGTYPE(name,num_t,ls_t) \
BOOL name  PARMS(ad, vp, copyf) \
{ \
   auto char *vpp; \
   argName_t  argname; \
   num_t  value; \
 \
   (VOID) get_argname( arg_sname(ad), argname ); \
   value = (num_t) strtol(vp, &vpp, 0); \
   if (*vpp != '\0') { \
      usrerr("invalid integer argument '%s' for %s", vp, argname); \
      return (FALSE); \
   } \
   else { \
      if ( ARG_isVEC(ad) ) { \
         ls_t  *vec = (ls_t *)arg_valp(ad); \
 \
         if ( (vec->count % BLOCKSIZE) == 0 ) { \
            vec->array = (num_t *) REALLOC(vec->array, VEC_SIZE(vec, num_t)); \
            if ( !vec->array ) \
               syserr("out of memory saving arg %s", argname); \
 \
            vec->flags = (argMask_t *) REALLOC(vec->flags, VEC_SIZE(vec, argMask_t)); \
            if ( !vec->flags ) \
               syserr("out of memory saving arg %s", argname); \
         } \
 \
         vec->flags[ vec->count ] = arg_flags(ad); \
         vec->array[ (vec->count)++ ] = value; \
      } \
      else \
         *(num_t *) arg_valp(ad) = value; \
 \
      return (TRUE); \
   } \
}


/* define argInt() */
INTEGRAL_ARGTYPE( argInt, int, intvec_t )

/* define argShort() */
INTEGRAL_ARGTYPE( argShort, short, shortvec_t )

/* define argLong() */
INTEGRAL_ARGTYPE( argLong, long, longvec_t )


#ifndef NOFLOAT

/***************************************************************************
** ^SECTION: FLOATING-POINT-TYPES -- argFloat, argDouble
**    Each of these functions converts the given string to the desired
**    floating-point type.
***^^**********************************************************************/

   /*
   ** macro to define a decimal argtype function
   **
   ** NOTE : do NOT use a terminating semicolon when invoking this macro!
   */
#define  DECIMAL_ARGTYPE(name,dec_t,ls_t) \
BOOL name  PARMS(ad, vp, copyf) \
{ \
   auto char *vpp; \
   argName_t  argname; \
   dec_t  value; \
 \
   (VOID) get_argname( arg_sname(ad), argname ); \
   value = (dec_t) strtod(vp, &vpp); \
   if (*vpp != '\0') { \
      usrerr("invalid decimal argument '%s' for %s", vp, argname); \
      return (FALSE); \
   } \
   else { \
      if ( ARG_isVEC(ad) ) { \
         ls_t  *vec = (ls_t *)arg_valp(ad); \
 \
         if ( (vec->count % BLOCKSIZE) == 0 ) { \
            vec->array = (dec_t *) REALLOC(vec->array, VEC_SIZE(vec, dec_t)); \
            if (!vec->array) \
               syserr("out of memory saving arg %s", argname); \
 \
            vec->flags = (argMask_t *) REALLOC(vec->flags, VEC_SIZE(vec, argMask_t)); \
            if (!vec->flags) \
               syserr("out of memory saving arg %s", argname); \
         } \
 \
         vec->flags[ vec->count ] = arg_flags(ad); \
         vec->array[ (vec->count)++ ] = value; \
      } \
      else \
         *(dec_t *) arg_valp(ad) = value; \
 \
      return (TRUE); \
   } \
}

/* define argFloat */
DECIMAL_ARGTYPE( argFloat, float, floatvec_t )

/* define argLong */
DECIMAL_ARGTYPE( argDouble, double, doublevec_t )

#endif  /* NOFLOAT */


/*************************************************************************
** ^SECTION: BOOLEAN-TYPES -- argBool, argSBool, argUBool, argTBool
**    ArgBool and argSBool set a boolean value (if no value is given).
**    ArgUBool unsets a boolean value (if no value is given). ArgTBool
**    toggles a boolean value (if no value is given). If a value is
**    supplied to any of these routines, then the string is looked up
**    in a table and assigned the corresponding value.
**
**    If a value is supplied for an argument that was matched via its
**    single character name and is part of the same argv element as the
**    argument-name (so that both ARGKEYWORD and ARGVALSEP are not set),
**    then only the first character of the value is used (unless it is
**    not found in our table, in which case the value is ignored and the
**    default action is taken).
**
**    The only possible arguments for single-character options are the
**    following:
**
**       1, +         set the flag
**       0, -         unset the flag
**       ^, ~         toggle the flag
**
**    The possible argument strings for long-options (keywords) are as
**    follows (case-insensitive):
*/

   /* define a structure for an item in our boolean-lookup table */
struct booltab {
   char   *bname;      /* string to match against */
   char   bneedmatch;  /* number of characters that must match */
   BOOL   bval;        /* value to use */
};

   /* define the boolean-lookup table */
STATIC struct booltab	_BoolTab[] = {
   "1",      1,   TRUE,
   "0",      1,   FALSE,
   "+",      1,   TRUE,
   "-",      1,   FALSE,
   "yes",    1,   TRUE,
   "no",     1,   FALSE,
   "true",   1,   TRUE,
   "false",  1,   FALSE,
   "on",     2,   TRUE,
   "off",    3,   FALSE,
   CHARNULL
};

/**^^**********************************************************************/


   /*
   ** NOTE: Lists and vectors of Boolean types are not supported!!!
   **       (same goes for argEnd, argInput, & argOutput)
   */

/*ARGSUSED*/
BOOL argBool  PARMS(ad, vp, copyf)
{
   register struct booltab *b;
   register char *cp;
   argName_t  argname;
   int len;

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

   /* ARGVECs are not supported for this Boolean arg-types */
   if ( ARG_isVEC(ad) )
      syserr( "Error in '%s' arg-entry! Boolean argvecs are not supported!",
            argname );

   /* if vp is NULL, just set to TRUE
   **    (needed for backward compatibility)
   */
   if ( !vp || !*vp ) {
      *(BOOL *) arg_valp(ad) = TRUE;
      return (TRUE);
   }

      /* allow single character arguments for non-keywords */
   if ( !BTEST(arg_flags(ad), ARGKEYWORD | ARGVALSEP) ) {
      if ( *vp == '+' || *vp == '1' ) {
         *(BOOL *) arg_valp(ad) = TRUE;
         return (BOOL) -1;
      }
      if ( *vp == '-' || *vp == '0' ) {
         *(BOOL *) arg_valp(ad) = FALSE;
         return (BOOL) -1;
      }
      if ( *vp == '~' || *vp == '^' ) {
         *(BOOL *) arg_valp(ad) = (*(BOOL *) arg_valp(ad)) ? FALSE : TRUE;
         return (BOOL) -1;
      }

         /* unmatched value, return FALSE for non-argBool (so the caller
         ** can use whatever default) and return TRUE for argBool.
         */
      if ( arg_type(ad) == argBool ) {
         *(BOOL *) arg_valp(ad) = TRUE;
         return  TRUE;
      }
      return  FALSE;
   }/* if single char option */

   /* copy input & convert to lower case */
   cp = strlwr( strdup(vp) );
   len = strlen( cp );

   /* search for a match in the table */
   for (b = _BoolTab; b->bname ; b++) {
      /* if too short, don't even bother trying */
      if (len < b->bneedmatch)
         continue;

      if ( memcmp(cp, b->bname, len) == 0) {
         /* got a match */
         *(BOOL *) arg_valp(ad) = b->bval;
         free( cp );
         return (TRUE);
      }
   }/*if match*/

   free( cp );
   usrerr("invalid Boolean argument '%s' for %s", vp, argname);
   return (FALSE);
}


/*ARGSUSED*/
BOOL argSBool  PARMS(ad, vp, copyf)
{
   argName_t  argname;
   BOOL  retval;

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

   /* ARGVECs are not supported for this Boolean arg-types */
   if ( ARG_isVEC(ad) )
      syserr( "Error in '%s' arg-entry! Boolean argvecs are not supported!",
            argname );

   /* if vp is NULL, just set to TRUE */
   if ( !vp || !*vp || !(retval = argBool(ad, vp, copyf)) ) {
      *(BOOL *) arg_valp(ad) = TRUE;
      return (TRUE);
   }
   else
      return  retval;
}

/*ARGSUSED*/
BOOL argUBool  PARMS(ad, vp, copyf)
{
   argName_t  argname;
   BOOL  retval;

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

   /* ARGVECs are not supported for this Boolean arg-types */
   if ( ARG_isVEC(ad) )
      syserr( "Error in '%s' arg-entry! Boolean argvecs are not supported!",
            argname );

   /* if vp is NULL, just set to FALSE */
   if ( !vp || !*vp || !(retval = argBool(ad, vp, copyf)) ) {
      *(BOOL *) arg_valp(ad) = FALSE;
      return (TRUE);
   }
   else
      return retval;
}

/*ARGSUSED*/
BOOL argTBool  PARMS(ad, vp, copyf)
{
   argName_t  argname;
   BOOL  retval;

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

   /* ARGVECs are not supported for this Boolean arg-types */
   if ( ARG_isVEC(ad) )
      syserr( "Error in '%s' arg-entry! Boolean argvecs are not supported!",
            argname );

   /* if vp is NULL, just toggle value */
   if ( !vp || !*vp || !(retval = argBool(ad, vp, copyf)) ) {
      *(BOOL *) arg_valp(ad) = (*(BOOL *) arg_valp(ad)) ? FALSE : TRUE ;
      return (TRUE);
   }
   else
      return retval;
}


#ifdef vms_style

/***************************************************************************
** ^SECTION: I/O-REDIRECTION-TYPES -- argInput, argOutput
**    ArgInput attempts to redirect the file-pointer addressed by ad_valp
**    to the file named by the given value. The file is opened for reading.
**
**    ArgOutput attempts to redirect the file-pointer addressed by ad_valp
**    to the file named by the given value. The file is opened for writing.
**
**    In either case, ad_valp should be of type (FILE **) (a pointer to a
**    file pointer) and not a mere file pointer as in (FILE *)!!!
**
**    If the given files cannot be opened, then an error message is printed
**    and the associated input/output streams are closed.
***^^**********************************************************************/

   /*
   ** slight problem here - on VMS, as_valp should be of type FILE **
   ** but on Unix (emulating VMS) it needs to be of type FILE *.
   ** we get around this using the following:
   */
# ifdef vms
#  define FP *fp
# else
#  define FP fp
# endif

/*ARGSUSED*/
BOOL argInput  PARMS(ad, vp, copyf)
{
#ifdef vms
   FILE **fp = (FILE **)arg_valp(ad);
#else
   FILE *fp = (FILE *)arg_valp(ad);
#endif
   BOOL error = FALSE;

   /* redirect file pointer to read from file */
   if ( !vp ) {
      usrerr( "Error: no file name given" );
      error = TRUE;
   }

   if ( !error  &&  !FP )
      error = TRUE;
   else if ( !error  &&  !freopen(vp, "r", FP) )
      error = TRUE;

   if ( error )  {
      usrerr( "Error: unable to redirect input to file \"%s.\"", vp );
      return  (FALSE);
   }
   return (TRUE);
}


/*ARGSUSED*/
BOOL argOutput  PARMS(ad, vp, copyf)
{
#ifdef vms
   FILE **fp = (FILE **)arg_valp(ad);
#else
   FILE *fp = (FILE *)arg_valp(ad);
#endif
   BOOL error = FALSE;

   /* redirect file pointer to write to file */
   if ( !vp ) {
      usrerr( "Error: no file name given" );
      error = TRUE;
   }

   if ( !error  &&  !FP )
      error = TRUE;
   else if ( !error  &&  !freopen(vp, "a", FP) )
      error = TRUE;

   if ( error )  {
      usrerr( "Error: unable to redirect output to file \"%s.\"", vp );
      return  (FALSE);
   }
   return (TRUE);
}

# undef FP
#endif  /* vms_style */
