#define UNIX 1
/* #define NOISY 1 */      /* Use this if you want error messages from parse */
/* #define DEMO 1 */
/* #define VAX  1 */
/* #define GENERAL 1 */  /* Use this to compile stuff which is also in
                           General.c */
/***************************************************************************

   Program:    
   File:       Parse.c 
   
   Version:    V1.2FPU
   Date:       11.07.90
   Function:   A command parser.
   
   Copyright:  SciTech Software 1991
   Author:     Andrew C. R. Martin
   Address:    SciTech Software
               23, Stag Leys,
               Ashtead,
               Surrey,
               KT21 2TD.
   Phone:      +44 (0372) 275775
   EMail:      UUCP: cbmuk!cbmuka!scitec!amartin
               JANET: andrew@uk.ac.ox.biop
   Written while at:
               Laboratory of Mathematical Biology
               National Institue for Medical Research,
               The Ridgeway,
               Mill Hill,
               London,
               NW7 1AA
               
****************************************************************************

   This program is not in the public domain, but it may be freely copied
   and distributed for no charge providing this header is included.
   The code may be modified as required, but any modifications must be
   documented so that the person responsible can be identified. If someone
   else breaks this code, I don't want to be blamed for code that does not
   work! The code may not be sold commercially without prior permission from
   the author, although it may be given away free with commercial products,
   providing it is made clear that this program is free and that the source
   code is provided with the program.

****************************************************************************

   Description:
   ============
      parse() is a command line parser which will accept upper or
      lower case commands and abbreviations. Comment lines may be
      indicated using a !. The keyword structure aray and returned
      string array are defined thus:
              KeyWd keywords[NCOMM];
              char  *strparam[MAXSTRPARAM];
      The returned float parameters are defined thus:
              float floatparam[MAXFLOATPARAM];
      Space for the returned strings must be allocated thus:
              strparam[n] = (char *)malloc(MAXSTRLEN * sizeof(char));
      and repeated for each parameter.

      The keyword list with type and numbers of returned parameters
      is constructed using the MAKEKEY macros:
              MAKEKEY(keywords[0],"RANGE",NUMBER,2);
              MAKEKEY(keywords[1],"STRING",STRING,1);
      Here, the keywords must be defined in upper case.

****************************************************************************

   Usage:
   ======
parse(comline,nkeys,keywords,floatparam,strparam)
Input:      char  *comline       A command line string to parse
            int   nkeys          Number of keywords
            KeyWd *keywords      Array of keyword structures
Output:     float *floatparam    Array of returned strings
            char  **strparam     Array of pointers to returned strings
Returns:    int                  Index of found command or error flag
****************************************************************************

   Revision History:
   =================
   V1.1   29.10.90
   match() now frees the memory it allocates and calls terminate()
   parse() now calls terminate() on the keyword string

   V1.2   25.09.91
   Messages will only appear from parse() if NOISY is #defined.
      

***************************************************************************/
#ifdef FPU
#include <m68881.h>
#endif

#include <stdio.h>
#include <string.h>
#include <math.h>

/* UNIX needs this to define toupper() */
#ifdef UNIX
#include <ctype.h>
#endif

#include "mylib:parse.h"

/************************************************************************/
/* General defines for these routines */

#define LF 10
#define CR 13
#define DIC 34   /* Double inverted commas */

/* The VAX confuses output to stdout and stderr when both are directed
   to the same place (e.g. the screen), so it's easier to send everything
   to stdout
*/
#ifdef VAX
#define OUTPUT stdout
#else
#define OUTPUT stderr
#endif

#ifdef GENERAL
/*************************************************************************
KillLeadSpaces()
Input:      char  *string        A character string
Returns:   *char                 A pointer to the string with the leading
                                 spaces removed
This routine strips leading spaces from a string.
*************************************************************************/
char *KillLeadSpaces(string)
char *string;
{
   while(*string == ' ') string++;
   return(string);
}
#else
extern char *KillLeadSpaces();
#endif



/*************************************************************************
parse()
Input:      char  *comline       A command line string to parse
            int   nkeys          Number of keywords
            KeyWd *keywords      Array of keyword structures
Output:     float *floatparam    Array of returned strings
            char  **strparam     Array of pointers to returned strings
Returns:    int                  Index of found command or error flag
*************************************************************************/
parse(comline,nkeys,keywords,floatparam,strparam)
char  *comline,
      **strparam;
KeyWd *keywords;
int   nkeys;
float *floatparam;
{
   char *command;
   int  i,n,found,nletters,nlett;

   command = KillLeadSpaces(comline);
   terminate(command);
   
   found = 0;
   if((command[0]=='!') ||
      (command[0]==LF)  ||
      (command[0]==CR)  ||
      (command[0]=='\0'))
      return(PARSE_COMMENT);

   for(i=0;i<nkeys;i++)
   {
      /* match() returns 1 if first string finishes first or exact match
                         2 if second string finishes first
                         0 if a mismatch
         We only want to act in the first case
      */
      if((n=match(command,(keywords[i]).name,&nletters))==1)
      {
#ifdef DEBUG
         printf("Matched letters: %d\n",nletters);
#endif
         if(found)  /* If found already */
         {
#ifdef NOISY
            fprintf(OUTPUT,"Ambiguous keyword: %s\n",command);
#endif
            return(PARSE_ERRC);
         }
         found = i+1;  /* +1, so keyword 0 will flag TRUE */
         nlett = nletters;
      }
   }
   if(!found)
   {
#ifdef NOISY
      fprintf(OUTPUT,"Keyword not known: %s\n",command);
#endif
      return(PARSE_ERRC);
   }
   command+=nlett;
   found--; /* Reset to point to the correct keyword */

#ifdef DEBUG
   printf("After getting command, line contains %s\n",command);
#endif

   /* Get data requirements for this keyword */
   if((keywords[found]).string)
   {
#ifdef DEBUG
      printf("Command expects strings\n");
#endif
      for(i=0; i<(keywords[found]).nparam; i++)
      {
         command = KillLeadSpaces(command);
         if((nletters = GetString(command,strparam[i]))==NULL)
         {
#ifdef NOISY
            fprintf(OUTPUT,"Missing string parameter in: %s\n",comline);
            fprintf(OUTPUT,"Command expects %d parameters\n",
                   (keywords[found]).nparam);
#endif
            return(PARSE_ERRP);
         }
         command += nletters;
      }  /* End of for(i) */
   }
   else
   {
      /* A numeric or no parameter */
#ifdef DEBUG
      printf("Command expects %d parameters\n",(keywords[found]).nparam);
#endif
      for(i=0; i<(keywords[found]).nparam; i++)
      {
         command = KillLeadSpaces(command);
         if(!GetParam(command,&(floatparam[i]),&nletters))
         {
#ifdef NOISY
            fprintf(OUTPUT,"Error in parameter %s\n",comline);
            fprintf(OUTPUT,"Command expects %d numeric parameters\n",
                   (keywords[found]).nparam);
#endif
            return(PARSE_ERRP);
         }
         command += nletters;
      }  /* End of for(i) */
   }  /* End of else */
   return(found);
}




/*************************************************************************
match()
Input:      char  *comstring     A character string
            char  *string2       A second string
Output:     int   *nletters      Number of letters matched
Returns:    int                  0 String mismatch
                                 1 First string finished first
                                 2 Second string finished first

This routine matches two strings, but stops the comparison as soon
as a space or NULL is found in either string. Th returned value
indicates which string finished first or 0 if the letters before the
space or NULL have a mismatch. The routine calls String ToUpper()
on `comstring' before the comparison.
*************************************************************************/
match(comstring,string2,nletters)
char *comstring,*string2;
int  *nletters;
{
   int  i;
   char *string1;

   terminate(comstring);
   terminate(string2);
   string1 = (char *)malloc((strlen(comstring) + 2) * sizeof(char));
   StringToUpper(comstring,string1);

#ifdef DEBUG
   printf("After U/C:  %s\n",string1);
   printf("Keyword:    %s\n\n",string2);
#endif

   for(i=0;;i++)
   {
      if((!string1[i])||(string1[i]==' '))
      {
         *nletters = i;
         return(1);
      }
      if((!string2[i])||(string2[i]==' '))
      {
         *nletters = i;
         return(2);
      }
      if(string1[i] != string2[i])
      {
         *nletters = i;
         return(0);
      }
   }
   free(string1);
}




/*************************************************************************
GetString()
Input:      char  *command       A character string
Output:     char  *strparam      Returned character string
Returns:    int                  Number of characters pulled out
                                 of the command string

This routine returns the first space-delimited group of characters
from character string `command'
*************************************************************************/
GetString(command,strparam)
char *command,*strparam;
{
   int i,j,inv_commas;

   inv_commas=0;
   j=0;
   for(i=0;;i++)
   {
      if(command[i]==DIC)
      {
         /* Toggle the inv_commas flag */
         inv_commas = abs(inv_commas-1);
         /* Don't copy anything */
         continue;
      }

      /* Break out if we're at the end of a line */
      if((command[i]==LF)
       ||(command[i]==CR)
       ||(command[i]=='\0')) break;

      /* Also break out if we've a space and we're not between
         inverted commas */
      if((command[i]==' ') && (!inv_commas)) break;

      /* Other wise copy the character */
      strparam[j++] = command[i];
   }
   strparam[j]='\0';
   return(i);
}



/*************************************************************************
GetParam()
Input:      char  *command       A character string
Output:     float *value         Returned float value
            int   *nletters      Number of charcters pulled out
                                 of the command string
Returns:    int                  0 If error
                                 1 If OK

This routine extracts the first space-delimited number from the
`command' character string.
*************************************************************************/
GetParam(command,value,nletters)
char  *command;
float *value;
int   *nletters;
{
   char buffer[50];
   int  retval;

   if((*nletters = GetString(command,buffer))==NULL)
      return(0);

   retval = sscanf(buffer,"%f",value);
   return(retval);
}



#ifdef GENERAL
/*************************************************************************
StringToUpper()
Input:      char  *string1       A character string
Output:     char  *string2       A second string

This routine converts a lower or mixed case string to upper case
*************************************************************************/
StringToUpper(string1,string2)
char *string1,*string2;
{
   int i;

   for(i=0;i<strlen(string1);i++)
      string2[i] = toupper(string1[i]);

   string2[i] = '\0';

   return(0);
}
#endif

/*************************************************************************
terminate(string)
Input/Output:   char  *string       A character string

This routine terminates a string at the first \n
*************************************************************************/
terminate(string)
char *string;
{
   int i=0;
   
   for(i=0;string[i];i++)
   {
      if(string[i] == '\n')
      {
         string[i] = '\0';
         break;
      }
   }
   return(0);
}


/*************************************************************************/
/*                                                                       */
/*                             DEMO CODE                                 */
/*                                                                       */
/*************************************************************************/

#ifdef DEMO

#define NCOMM 9                  /* Number of keywords                   */
#define MAXNUMPARAM 10           /* Max number of numeric parameters     */
#define MAXSTRPARAM 2            /* Max number of string parameters      */
#define MAXSTRLEN   80           /* Max length of retuyrned string       */
main()
{
   KeyWd keywords[NCOMM];        /* Array to store keywords              */
   char  *strparam[MAXSTRPARAM], /* Array for returned strings           */
         comline[100];           /* The command line for parsing         */
   int   key;                    /* Return value from parse()            */
   float numparam[MAXNUMPARAM];  /* Array for returned numbers           */
   int   i;                      /* Counter                              */

   /* Initialise returned string array */
   for(i=0; i<MAXSTRPARAM; i++)
      strparam[i] = (char *)malloc(MAXSTRLEN * sizeof(char));

   /* Construct the keywords */
   MAKEKEY(keywords[0],"END",       NUMBER,0);
   MAKEKEY(keywords[1],"STRING",    STRING,1);
   MAKEKEY(keywords[2],"FLOAT",     NUMBER,1);
   MAKEKEY(keywords[3],"INT",       NUMBER,1);
   MAKEKEY(keywords[4],"NOTHING",   NUMBER,0);
   MAKEKEY(keywords[5],"NULL",      NUMBER,0);
   MAKEKEY(keywords[6],"TWOSTRINGS",STRING,2);
   MAKEKEY(keywords[7],"RANGE",     NUMBER,2);
   MAKEKEY(keywords[8],"HELP",      NUMBER,0);

   /* Print a message */
   printf("Known Commands:\nRANGE, STRING, FLOAT, INT, NOTHING, NULL, \
TWOSTRINGS, END, HELP\n");

   /* Start a loop in which we read a string from the keyboard
      and parse it. The keyword list is designed so END returns 0
   */
   key = 1;
   while(key)
   {
      printf("Test> ");
      gets(comline);
      key = parse(comline,NCOMM,keywords,numparam,strparam);

      /* Carry out appropriate action for each keyword. This
         could be made more readable by #define'ing a label for
         each key number which matches the command name */
      switch(key)
      {
      case PARSE_ERRC:
      case PARSE_ERRP:
      case PARSE_COMMENT:
         break;
      case 0:
         printf("END: Goodbye!\n");
         break;
      case 1:
         printf("STRING: %s\n",strparam[0]);
         break;
      case 2:
         printf("FLOAT: Value %f\n",numparam[0]);
         break;
      case 3:
         printf("INT:   Value %f\n",numparam[0]);
         break;
      case 4:
         printf("NOTHING\n");
         break;
      case 5:
         printf("NULL\n");
         break;
      case 6:
         printf("TWOSTRINGS:\n%s\n%s\n",strparam[0],strparam[1]);
         break;
      case 7:
         printf("RANGE: Values %f,%f\n",numparam[0],numparam[1]);
         break;
      case 8:
         printf("Known Commands:\nRANGE, STRING, FLOAT, INT, NOTHING, NULL, \
TWOSTRINGS, END, HELP\n");
      }
   }
}
#endif
