#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 inGeneral.c */

/***************************************************************************

___000008__

   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 double parameters are defined thus:
              double doubleparam[MAXdoublePARAM];
      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,doubleparam,strparam)
Input:      char  *comline       A command line string to parse
            int   nkeys          Number of keywords
            KeyWd *keywords      Array of keyword structures
Output:     double *doubleparam    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 <math.h>

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

#include "edt.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
#ifdef AMIGA
extern BPTR     __STDERR;
#define fprintf	FPrintf
#define OUTPUT __STDERR
#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:     double *doubleparam    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, doubleparam, strparam)
char           *comline,
              **strparam;
KeyWd          *keywords;
int             nkeys;
double         *doubleparam;
{
	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: %ld\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 %ld 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 %ld parameters\n", (keywords[found]).nparam);
#endif
		for (i = 0; i < (keywords[found]).nparam; i++)
		{
			command = KillLeadSpaces(command);
			if (!GetParam(command, &(doubleparam[i]), &nletters))
			{
#ifdef NOISY
				fprintf(OUTPUT, "Error in parameter %s\n", comline);
				fprintf(OUTPUT, "Command expects %ld 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:     double *value         Returned double 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;
double         *value;
int            *nletters;
{
	char            buffer[50];
	int             retval;

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

	retval = sscanf(buffer, "%lf", 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()            */
	double          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], "DOUBLE", 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, DOUBLE, 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("double: 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
