/********************************************************************
 * Parse.c (C) copyright 1987 John M. Olsen
 *
 * /|  |    /|||  /\|		|	John M. Olsen
 * \|()|\|\_ |||. \/|/)@|\_	|	1547 Jamestown Drive
 *  |				|	Salt Lake City, UT  84121-2051
 * u-jmolse@ug.utah.edu  or  ...!{seismo,ihnp4}!utah-cs!utah-ug!u-jmolse
 *
 * This program or any significant portion of it may not be used in a
 * commercial product without written permission of the author.  (My
 * permission is easy to get).
 *
 * Feel free to distribute this code as part of any public domain collection,
 * or hack on it to make it more user friendly, as long as you try to
 * document your changes as not being my code.
 *
 * This is a recursive descent exprsession parser, based very loosely on one
 * found in Herbert Schildt's book "Advanced C", published by McGraw-Hill.
 * It parses expressions by having each layer either recognize something that
 * it can do, or passing it on to the next function which does higher
 * precedence operators.
 *
 * It has very minimal error checking, and does not check for overflow or
 * underflow on calculations.  If it detects an error, it will give a message
 * and what it thinks the correct result was up to that time.
 *
 * It converts expressions to lower case so it only needs to look for math
 * function names spelled one way.
 *
 * It uses standard I/O, so it must be used from the CLI.  This makes it
 * very easy to port to other machines.
 *
 * Aztec instructions using fast floating point:
 *
 * cc parse.c
 * ln parse.o -lm -lc
 *
 * Aztec 4.30a using IEEE double precision floating point: (Big difference!)
 *
 * cc parse.c +fi
 * ln parse.o -lmx -lc
 *
 * It has also been (fairly) successfully compiled on a VAX1180, but it
 * complained at expressions on the argument list.  The only modification
 * required was to comment out the #include <functions.h> command.
 *
 *********************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <functions.h>
#include <math.h>

#define PI ((double)3.14159265358979)
#define E  ((double)2.71828182845904)

#define TOK_SIZE 20	/* Tokens are no longer than this number. */
#define EXP_SIZE 80	/* Expressions no longer than this number. */

#define TRUE 1
#define FALSE 0

extern double atof(), binary(), fn_eval();
extern double parse(), parse2(), parse3(), parse4(), parse5();

/* Here is a simple calling program to test it. */

main (argc, argv)
int argc;
char *argv[];
{
	char exprs[EXP_SIZE];
	double ans;
	int loop;

	if(argc < 2)
	{
		ans = 0;
		printf("Type '?' for help.\n");
		printf("Enter exprsression: ");
		gets (exprs);
	}
	else
	{
		exprs[0] = NULL;
		for(loop = 1; loop < argc; loop++)
			strcat(exprs, argv[loop]);
	}
	for(loop = 0; loop < strlen(exprs); loop++) /* convert to lower case */
		if(isalpha(exprs[loop]))
			exprs[loop] = tolower(exprs[loop]);
	if(exprs[0] == '?' || exprs[0] == 'h')
	{	/* help menu */
		printf("Supported binary operators: + - / % ^\n");
		printf("Supported unary operators:  + -\n");
		printf("Supported unary functions:  sin, cos, tan,\n");
		printf("	asin, acos, atan, sinh, cosh, tahn,\n");
		printf("	log, logten, abs, sqrt, int.\n");
		printf("Defined constants:          pi, e.\n\n");
		printf("Usage: %s (expression)\n", argv[0]);
		printf("   or: %s ?\n", argv[0]);
		exit(0);
	}
	ans = parse (exprs);
	if(exprs[0])
		printf("Bad expression.\n");
	else
		printf ("%f\n", ans);
}

/*******************************************************************
The main parser function. It parses + and -.
*******************************************************************/
double parse (exprs)
char exprs[];
{
	char tok[TOK_SIZE];	/* this should only be a + or - character. */
	double a;

	a = parse2 (exprs);
	while (exprs[0])
	{
		get_token (tok, exprs);
		if (tok[0] == '+' || tok[0] == '-')
			a = binary (a, parse2 (exprs), tok);
		else
			return (a);
	}
	return (a);
}

/*******************************************************************
The secondary parser, which parses * and /.
*******************************************************************/
double parse2 (exprs)
char *exprs;
{
	char tok[TOK_SIZE];
	double a;

	a = parse3 (exprs);
	while (exprs[0])
	{
		get_token (tok, exprs);
		if (tok[0] == '*' || tok[0] == '/' || tok[0] == 'm'
					 || tok[0] == 'd')
			a = binary (a, parse3 (exprs), tok);
		else
		{
			unget_tok (tok, exprs);
			return (a);
		}	
	}
	return (a);
}

/*******************************************************************
The third parser, which parses ^.
*******************************************************************/
double parse3 (exprs)
char *exprs;
{
	char tok[TOK_SIZE];
	double a;

	a = parse4 (exprs);
	while (exprs[0])
	{
		get_token (tok, exprs);
		if (tok[0]=='^')
			a = (binary (a, parse4 (exprs), tok));
		else
		{
			unget_tok (tok, exprs);
			return (a);
		}	
	}
	return (a);
}

/*******************************************************************
The fourth parser, which parses unary minus and plus.
*******************************************************************/
double parse4 (exprs)
char *exprs;
{
	char tok[TOK_SIZE];

	get_token (tok, exprs);
	if (tok[0] == '-')
		return (-(parse4 (exprs)));
	else if(tok[0] == '+')
		return(parse4(exprs));
	unget_tok (tok, exprs);
	return (parse5 (exprs));
}

/*******************************************************************
The fifth parser, which parses functions and parentheses.
*******************************************************************/
double parse5 (exprs)
char *exprs;
{
	char tok[TOK_SIZE], buff[EXP_SIZE];
	double a;

	get_fn (tok, exprs);
	if (tok[0])	/* a function call. */
		return (fn_eval (tok, exprs));
	get_numb (tok, exprs);
	if (tok[0])	/* A number. */
		return (atof (tok));
	if (exprs[0] == '(')	/* a parenthesized exprsression. */
	{	/*strip open paren, call parse, strip close paren. */
		strcpy (buff, exprs + 1);
		strcpy (exprs, buff);
		a = parse (exprs);
		if (exprs[0] == ')')
		{
			strcpy (buff, exprs + 1);
			strcpy (exprs, buff);
		}
		else
		{	/* Error if here. */
			printf("Bad parentheses.\n");
		}
		return (a);
	}
	printf("Bad parentheses.\n");
}

/*******************************************************************
Return a pointer to a string with the token in it.  A token may be
at most TOK_SIZE characters.  All after that are tossed into the
bit bucket.
*******************************************************************/
get_token (tok, exprs)
char *tok, *exprs;
{
	char temp[EXP_SIZE];

	tok[0] = 0;
	while (iswhite (exprs[0]))	/* move from exprs[1] to exprs[0] */
	{
		strcpy (temp, exprs + 1);
		strcpy (exprs, temp);
	}
	sscanf (exprs, "%1[+-*/^]", tok);
	strcpy (temp, exprs + strlen (tok));
	strcpy (exprs, temp);
}

/*******************************************************************
Get a number from the front of the exprsression.
*******************************************************************/
get_numb (tok, exprs)
char *tok, *exprs;
{
	char temp[EXP_SIZE];

	tok[0] = 0;
	while (iswhite (exprs[0]))	/* move from exprs[1] to exprs[0] */
	{
		strcpy (temp, exprs + 1);
		strcpy (exprs, temp);
	}
	sscanf (exprs, "%[0123456789.]", tok);
	strcpy (temp, exprs + strlen (tok));
	strcpy (exprs, temp);
}
/*******************************************************************
Get a function name from the front of the exprsression.
*******************************************************************/
get_fn (tok, exprs)
char *tok, *exprs;
{
	char temp[EXP_SIZE];

	tok[0] = 0;
	while (iswhite (exprs[0]))	/* move from exprs[1] to exprs[0] */
	{
		strcpy (temp, exprs + 1);
		strcpy (exprs, temp);
	}
	sscanf (exprs, "%[abcdefghijklmnopqrstuvwxyz]", tok);
	strcpy (temp, exprs + strlen (tok));
	strcpy (exprs, temp);
}

/*******************************************************************
Replace a token at the front of the exprsression.
*******************************************************************/
unget_tok (tok, exprs)
char *tok, *exprs;
{
	char buff[EXP_SIZE];

	strcpy (buff, exprs);
	strcpy (exprs, tok);
	strcat (exprs, buff);
}

/*******************************************************************
This fn returns true if the key given to it is white space.
*******************************************************************/
iswhite (key)
char key;
{
	if (key == ' ' || key == 9 || key == 13)
		return (1);
	return (0);
}
/*******************************************************************
This fn returns (a operator b), or just a if an error occurs.
*******************************************************************/
double binary (a, b, tok)
double a, b;
char *tok;
{
	int loop;
	double c;

	switch (tok[0])
	{
		case '+':
			return (a + b);
		case '-':
			return (a - b);
		case '*':
			return (a * b);
		case '/':
			if (!near_zero (b))	/* if b != 0 */
				return (a / b);
			else
			{	/* division by zero error message. */
				printf("Division by zero error.\n");
			}
			return (a);
		case '^':	/* a to the b power. */
		{
			c = a;
			if (near_zero (b))	
				return (a);
			return(pow(a,b));
			/* non-function if you don't like the pow function */
			for (loop = (int) b - 1;loop > 0; --loop)
				a = a * c;
			return (a);
		}
		default:
			printf("No such token as %c\n", tok[0]);
	}
}

/*******************************************************************
Evaluates the function on the front of exprs.
*******************************************************************/
double fn_eval (tok, exprs)
char *exprs, *tok;
{
	if (tok[0])
	{
		if (!strcmp(tok, "abs"))
			return(fabs (parse5(exprs)));
		if (!strcmp(tok, "sqrt"))
			return(sqrt (parse5(exprs)));
		if (!strcmp(tok, "tan"))
			return(tan (parse5(exprs)));
		if (!strcmp(tok, "sin"))
			return(sin (parse5(exprs)));
		if (!strcmp(tok, "cos"))
			return(cos (parse5(exprs)));
		if (!strcmp(tok, "atan"))
			return(atan (parse5(exprs)));
		if (!strcmp(tok, "asin"))
			return(asin (parse5(exprs)));
		if (!strcmp(tok, "acos"))
			return(acos (parse5(exprs)));
		if (!strcmp(tok, "tanh"))
			return(tanh (parse5(exprs)));
		if (!strcmp(tok, "sinh"))
			return(sinh (parse5(exprs)));
		if (!strcmp(tok, "cosh"))
			return(cosh (parse5(exprs)));
		if (!strcmp(tok, "pi"))
			return(PI);
		if (!strcmp(tok, "e"))
			return(E);
		if (!strcmp(tok, "log"))
			return(log(parse5(exprs)));
		if (!strcmp(tok, "logten"))
			return(log10(parse5(exprs)));
		if (!strcmp(tok, "int"))
			return((double)((long)(parse5(exprs))));
	}
	printf("Could not find expression %s\n",tok);
	unget_tok(tok, exprs);
}

/*******************************************************************
Returns true if num is near zero. It's used in division checks.
*******************************************************************/
near_zero (num)
double num;
{
	if (fabs (num) < .000000001)
		return (1);
	return (0);
}
