/*
 * Mathomatic help command and parsing routines.
 *
 * Everything that depends on the command table goes here.
 *
 * Copyright (C) 1987-2007 George Gesslein II.
 */

#include "includes.h"

/*
 * The following structure is used for each Mathomatic command.
 */
typedef	struct {
	char	*name;			/* command name to be typed by user (must not contain any spaces) */
	char	*secondary_name;	/* another name for this command */
	int	(*func)();		/* function that handles this command */
					/* function is passed a char pointer and returns true if successful */
	char	*usage;			/* command syntax text */
	char	*info;			/* one line description of command */
} com_type;

/*
 * The Mathomatic command table follows.  It should be in alphabetical order.
 */
static com_type com_list[] = {
/*	command name,	alternate name,		function,		usage,							information */
{	"approximate",	NULL,			approximate_cmd,	"[equation-number-ranges]",				"Approximate all numerical values in the specified equation spaces." },
#if	!LIBRARY
{	"calculate",	NULL,			calculate_cmd,		"[variable number-of-iterations]",			"Temporarily plug in values for variables and approximate." },
#endif
{	"clear",	NULL,			clear_cmd,		"[equation-number-ranges]",				"Delete expressions stored in memory so equation spaces can be reused." },
#if	!LIBRARY
{	"code",		NULL,			code_cmd,		"[\"c\" or \"java\" or \"python\" or \"integer\"] [equation-number-ranges]",	"Output C, Java, or Python code for the specified equations." },
#endif
{	"compare",	NULL,			compare_cmd,		"equation-number [\"with\" equation-number]",		"Compare two equation spaces to see if they are the same." },
{	"copy",		NULL,			copy_cmd,		"[equation-number-range]",				"Duplicate the specified equation spaces." },
{	"derivative",	"differentiate",	derivative_cmd,		"[variable or \"all\"] [order]",			"Symbolically differentiate and simplify, order times." },
{	"display",	NULL,			display_cmd,		"[\"factor\"] [equation-number-range]",			"Display equation spaces in multi-line fraction format." },
#if	!LIBRARY
{	"divide",	NULL,			divide_cmd,		"[variable]",						"Prompt for 2 polynomials/numbers and divide.  Display result and GCD." },
#endif
{	"echo",		NULL,			echo_cmd,		"[text]",						"Output a line of text, followed by a newline." },
#if	!LIBRARY && (UNIX || CYGWIN) && !SECURE
{	"edit",		NULL,			edit_cmd,		"[file-name]",						"Edit all equation spaces or an input file." },
#endif
{	"eliminate",	NULL,			eliminate_cmd,		"variables or \"all\" [\"using\" equation-number]",	"Substitute the specified variables with solved equations." },
{	"extrema",	"stationary",		extrema_cmd,		"[variable] [order]",					"Find possible local minimums and maximums of the current expression." },
{	"factor",	NULL,			factor_cmd,		"[\"number\" [integers]] or [equation-number-range] [variables]",	"Factor given integers or factor variables in expressions." },
{	"fraction",	NULL,			fraction_cmd,		"[equation-number-range]",				"Convert expressions with algebraic fractions into a single fraction." },
{	"help",		NULL,			help_cmd,		"[topic or command-names]",				"Short, built-in help." },
{	"imaginary",	NULL,			imaginary_cmd,		"[variable]",						"Copy the imaginary part of an expression (see the \"real\" command)." },
{	"integrate",	"integral",		integrate_cmd,		"[\"definite\"] variable [order]",			"Symbolically integrate polynomials, order times." },
{	"laplace",	NULL,			laplace_cmd,		"[\"inverse\"] variable",				"Compute the Laplace or inverse Laplace transform of polynomials." },
{	"limit",	NULL,			limit_cmd,		"variable expression",					"Take the limit as variable goes to expression." },
{	"list",		NULL,			list_cmd,		"[\"export\" or \"maxima\"] [equation-number-ranges]",	"Display equation spaces in single line format." },
#if	!LIBRARY
{	"nintegrate",	NULL,			nintegrate_cmd,		"[\"trapezoid\"] variable [partitions]",		"Approximate the definite integral using Simpson's rule."},
{	"optimize",	NULL,			optimize_cmd,		"[equation-number-range]",				"Split up an equation into smaller multiple equations." },
#endif
#if	!LIBRARY
{	"pause",	NULL,			pause_cmd,		"[text]",						"Wait for user to press the Enter key.  Optionally display a message." },
#endif
{	"product",	NULL,			product_cmd,		"variable start end [step]",				"Compute the product as variable goes from start to end." },
#if	READLINE
{	"push",		NULL,			push_cmd,		"[equation-number-range]",				"Push specified equation spaces into the readline history." },
#endif
#if	!LIBRARY
{	"quit",		"exit",			quit_cmd,		"",							"Terminate this program without saving." },
#endif
#if	!SECURE
{	"read",		NULL,			read_cmd,		"file-name",						"Read in a text file as if it was typed in." },
#endif
{	"real",		NULL,			real_cmd,		"[variable]",						"Copy the real part of an expression (see the \"imaginary\" command)." },
{	"replace",	NULL,			replace_cmd,		"[variables [\"with\" expression]]",			"Substitute variables in the current equation with expressions." },
#if	!LIBRARY
{	"roots",	NULL,			roots_cmd,		"root real-part imaginary-part",			"Display all the roots of a complex number." },
#endif
#if	!SECURE
{	"save",		NULL,			save_cmd,		"file-name",						"Save all equation spaces in a text file." },
#endif
{	"set",		NULL,			set_cmd,		"[[\"no\"] option]",					"Set or display various session options." },
{	"simplify",	NULL,			simplify_cmd,		"[\"symbolic\"] [\"quick\"] [\"fraction\"] [equation-number-range]",	"Completely simplify expressions." },
{	"solve",	NULL,			solve_cmd,		"variable or \"0\"",					"Solve the current equation for a variable or for zero." },
{	"sum",		NULL,			sum_cmd,		"variable start end [step]",				"Compute the summation as variable goes from start to end." },
#if	!LIBRARY
{	"tally",	NULL,			tally_cmd,		"[\"average\"]",					"Prompt for and add entries, show running (grand) total." },
#endif
{	"taylor",	NULL,			taylor_cmd,		"[variable] [order] [point]",				"Compute the Taylor series expansion of the current expression." },
{	"unfactor",	"expand",		unfactor_cmd,		"[\"fully\"] [equation-number-range]",			"Algebraically expand (multiply out) expressions." },
{	"version",	NULL,			version_cmd,		"",							"Display version number, compile flags, and maximum memory usage." }
};

/*
 * Process equation and expression input in Mathomatic, with no solving
 * and no automatic calculation.
 *
 * Parse the equation or expression text in "cp" and place in equation space "n".
 *
 * Return true if successful.
 */
int
parse(n, cp)
int	n;
char	*cp;
{
	if (parse_equation(n, cp)) {
		if (n_lhs[n] == 0 && n_rhs[n] == 0)
			return true;
		if (n_lhs[n] == 0) {
			/* RHS expression only, set equal to zero */
			n_lhs[n] = 1;
			lhs[n][0] = zero_token;
		}
		cur_equation = n;
		return return_result(cur_equation);
	}
	n_lhs[n] = 0;
	n_rhs[n] = 0;
	return false;
}

/*
 * Process equation and expression input in Mathomatic.
 *
 * Parse the expression text in "cp" and solve the current equation for it
 * or place it in equation space "n" if it is not a solve variable.
 *
 * Return true if successful.
 */
int
process_parse(n, cp)
int	n;
char	*cp;
{
	int	i;

	if (parse_equation(n, cp)) {
		if (n_lhs[n] == 0 && n_rhs[n] == 0) {
			if (strcmp(cp, "=") == 0 && n_lhs[cur_equation] > 0 && n_rhs[cur_equation] > 0) {
				debug_string(0, _("Swapping equation sides of the current equation."));
				n = cur_equation;
				i = n_lhs[n];
				blt(tes, lhs[n], n_lhs[n] * sizeof(token_type));
				n_lhs[n] = n_rhs[n];
				blt(lhs[n], rhs[n], n_rhs[n] * sizeof(token_type));
				n_rhs[n] = i;
				blt(rhs[n], tes, i * sizeof(token_type));
				return return_result(cur_equation);
			}
			return true;
		}
		if (n_lhs[n] == 0 || n_rhs[n] == 0) {
			if (autosolve) {
				if ((n_lhs[n] == 1 && ((lhs[n][0].kind == CONSTANT && lhs[n][0].token.constant == 0.0)
				    || (lhs[n][0].kind == VARIABLE && (lhs[n][0].token.variable & VAR_MASK) > SIGN)))
				    || (n_rhs[n] == 1 && ((rhs[n][0].kind == CONSTANT && rhs[n][0].token.constant == 0.0)
				    || (rhs[n][0].kind == VARIABLE && (rhs[n][0].token.variable & VAR_MASK) > SIGN)))) {
					if (solve(n, cur_equation)) {
						return return_result(cur_equation);
					}
					return false;
				}
			}
			if (n_rhs[n]) {
				/* RHS expression only, set equal to zero */
				n_lhs[n] = 1;
				lhs[n][0] = zero_token;
				goto return_ok;
			}
			if (n_lhs[n] == 1 && lhs[n][0].kind == CONSTANT && fmod(lhs[n][0].token.constant, 1.0) == 0.0
			    && lhs[n][0].token.constant > 0.0 && lhs[n][0].token.constant <= n_equations) {
				/* easy selecting of equation spaces */
				n_lhs[n] = 0;
				n = lhs[n][0].token.constant - 1;
				goto return_ok;
			}
#if	!LIBRARY
			if (autocalc) {
				/* the numerical input calculation */
				if (!exp_is_numeric(lhs[n], n_lhs[n])) {
					goto return_ok;		/* not numerical (contains a variable) */
				}
				/* make the expression an equation */
				blt(rhs[n], lhs[n], n_lhs[n] * sizeof(token_type));
				n_rhs[n] = n_lhs[n];
				lhs[n][0].level = 1;
				lhs[n][0].kind = VARIABLE;
				parse_var(&lhs[n][0].token.variable, "answer");
				n_lhs[n] = 1;
				i = cur_equation;
				/* make the current equation and run the calculate command on it */
				cur_equation = n;
				calculate_cmd("");
				cur_equation = i;
				/* delete from memory */
				n_lhs[n] = 0;
				n_rhs[n] = 0;
				return true;
			}
#endif
		}
return_ok:
		cur_equation = n;
		return return_result(cur_equation);
	}
	n_lhs[n] = 0;
	n_rhs[n] = 0;
	return false;
}

/*
 * Process a line of input to Mathomatic.
 * It may be a command, an expression, an equation, etc.
 *
 * Return true if successful.
 */
int
process(cp)
char	*cp;
{
	char	*cp1;
	char	*cp_start;
	int	i;
	int	rv;
	char	buf2[MAX_CMD_LEN];
	int	i1;
	char	*filename;
	FILE	*fp;
	int	append_flag;

	if (cp == NULL) {
		return false;
	}
	set_sign_array();
	cp_start = cp;
	cp = skip_space(cp);
	if (*cp == '#') {	/* handle the equation number selector */
		cp++;
		switch (*cp) {
		case '+':
		case '-':
			i = decstrtol(cp, &cp1);
			i = cur_equation + i;
			break;
		default:
			i = decstrtol(cp, &cp1) - 1;
			break;
		}
		if (cp == cp1)
			return true;	/* treat as comment */
		cp = cp1;
		if (i < 0 || i >= n_equations) {
			error(_("Equation not defined."));
			return false;
		}
		if (*cp == ':') {
			cp++;
		}
		cp = skip_space(cp);
		if (*cp) {
			input_column += (cp - cp_start);
			return parse(i, cp);
		}
		cur_equation = i;
		return return_result(cur_equation);
	}
#if	!SECURE
	/* handle shell escape */
	if (*cp == '!') {
		cp1 = getenv("SHELL");
		if (cp1 == NULL) {
			cp1 = "/bin/sh";
		}
		if (access(cp1, X_OK)) {
			error("Shell not found or not executable, check SHELL environment variable.");
			return false;
		}
		cp = skip_space(cp + 1);
		rv = shell_out(*cp ? cp : cp1);
		if (rv) {
			error(_("System command failed."));
		}
		return !rv;
	}
#endif
	/* a quick way to get help */
	if (*cp == '?') {
		cp = skip_space(cp + 1);
		input_column += (cp - cp_start);
		return(help_cmd(cp));
	}
/* See if the string pointed to by "cp" is a command. */
/* If so, execute it. */
	cp1 = cp;
	while (*cp1 && !isspace(*cp1))
		cp1++;
	for (i = 0; i < ARR_CNT(com_list); i++) {
		if (((cp1 - cp) >= min(4, strlen(com_list[i].name)) && strncasecmp(cp, com_list[i].name, cp1 - cp) == 0)
		    || (com_list[i].secondary_name && (cp1 - cp) >= min(4, strlen(com_list[i].secondary_name)) && strncasecmp(cp, com_list[i].secondary_name, cp1 - cp) == 0)) {
			cp1 = skip_space(cp1);
			input_column += (cp1 - cp_start);
			if (my_strlcpy(buf2, cp1, sizeof(buf2)) >= sizeof(buf2)) {
				error(_("Command line too long."));
				return false;
			}
			fp = NULL;
#if	!SECURE
			/* handle output redirection */
			append_flag = false;
			filename = NULL;
			for (i1 = strlen(buf2) - 1; i1 >= 0; i1--) {
				if (buf2[i1] == '>') {
					filename = skip_space(&buf2[i1+1]);
					if (i1 && buf2[i1-1] == '>') {
						i1--;
						append_flag = true;
					}
					buf2[i1] = '\0';
					break;
				}
			}
			if (filename) {
				if (append_flag) {
					fp = fopen(filename, "a");
				} else {
					fp = fopen(filename, "w");
				}
				if (fp == NULL) {
					error(_("Can't open redirected output file for writing."));
					return false;
				}
				gfp = fp;
			}
#endif
			/* remove trailing spaces from the command line */
		        i1 = strlen(buf2) - 1;
		        while (i1 >= 0 && isspace(buf2[i1])) {
        		        buf2[i1] = '\0';
                		i1--;
			}
			/* execute the command */
			rv = (*com_list[i].func)(buf2);
#if	!SECURE
			if (fp) {	/* if output redirected, close output file */
				if (gfp != stdout)
					fclose(gfp);
				else
					fclose(fp);
				gfp = stdout;
			}
#endif
			return rv;
		}
	}
/* "cp" is not a command, so parse the expression or equation. */
	i = next_espace();
	input_column += (cp - cp_start);
	return process_parse(i, cp);
}

#if	!SECURE
/*
 * Execute a system command.  Note that system(3) requires "/bin/sh".
 *
 * Returns exit value of command (0 if no error).
 */
int
shell_out(cp)
char	*cp;
{
	int	rv;

	reset_attr();
	rv = system(cp);
	if (rv < 0) {
		perror("system(3) call failed");
	}
	default_color();
	return rv;
}
#endif

/*
 * Parse a variable name with before and after space character skipping.
 *
 * Return new position in string or NULL if error.
 */
char	*
parse_var2(vp, cp)
long	*vp;	/* pointer to returned variable in Mathomatic internal format */
char	*cp;	/* pointer to variable name string */
{
	cp = skip_space(cp);
	cp = parse_var(vp, cp);
	if (cp == NULL) {
		return NULL;
	}
	return skip_space(cp);
}

#define P(A)	fprintf(gfp, "%s\n", A)

/*
 * Output command info and usage.
 *
 * Return the number of lines output.
 */
int
display_command(i)
int	i;	/* command table index of command */
{
	int	rows = 3;

	fprintf(gfp, "%s - %s\n", com_list[i].name, com_list[i].info);
	fprintf(gfp, "Usage: %s %s\n", com_list[i].name, com_list[i].usage);
	if (com_list[i].secondary_name) {
		fprintf(gfp, "Alternate name for this command: %s\n", com_list[i].secondary_name);
		rows++;
	}
	fprintf(gfp, "\n");
	return rows;
}

/*
 * The help command.
 */
int
help_cmd(cp)
char	*cp;
{
	int	i, j;
	char	*cp1;
	int	flag;
	int	row;

	cp1 = cp;
	while (*cp1 && !isspace(*cp1))
		cp1++;
	if (cp1 != cp) {
		/* first, see if the argument matches any command names */
		flag = false;
next_argument:
		for (i = 0; i < ARR_CNT(com_list); i++) {
			if (strncasecmp(cp, com_list[i].name, cp1 - cp) == 0
			    || (com_list[i].secondary_name && strncasecmp(cp, com_list[i].secondary_name, cp1 - cp) == 0)) {
				display_command(i);
				flag = true;
			}
		}
		if (*cp == '!') {
			P("! - Shell escape, execute shell and any specified system command.");
			P("Usage: ![system-command]\n");
			flag = true;
		}
		if (flag) {
			cp1 = cp = skip_space(cp1);
			while (*cp1 && !isspace(*cp1))
				cp1++;
			if (cp1 != cp) {
				goto next_argument;
			}
			return true;
		}
		if (strncasecmp(cp, "usage", cp1 - cp) == 0) {
			P("Command Usage Syntax");
			P("--------------------");
			for (i = 0, row = 3; i < ARR_CNT(com_list); i++) {
				fprintf(gfp, "%s %s\n", com_list[i].name, com_list[i].usage);
				row++;
				if (gfp == stdout && screen_rows && row >= (screen_rows - 1)) {
					row = 1;
					if (!pause_cmd(""))
						return false;
				}
			}
			return true;
		}
		if (strncasecmp(cp, "geometry", cp1 - cp) == 0) {
			P("Commonly Used Geometric Formulas");
			P("--------------------------------");
			P("Triangle of base \"b\" and height \"h\":");
			P("    Area = b*h/2");
			P("Rectangle of length \"l\" and width \"w\":");
			P("    Area = l*w                    Perimeter = 2*l + 2*w");
			P("Trapezoid of parallel sides \"a\" and \"b\", and \"d\" distance between them:");
			P("    Area = d*(a + b)/2");
			P("Circle of radius \"r\":");
			P("    Area = pi*r^2                 Perimeter = 2*pi*r");
			P("    Area = Perimeter*r/2");
			P("Rectangular solid of length \"l\", width \"w\", and height \"h\":");
			P("    Volume = l*w*h                Surface area = 2*l*w + 2*l*h + 2*w*h");
			P("Sphere of radius \"r\":");
			P("    Volume = 4/3*pi*r^3           Surface area = 4*pi*r^2");
			P("Right circular cylinder of radius \"r\" and height \"h\":");
			P("    Volume = pi*r^2*h             Surface area = 2*pi*r*(h + r)");
			P("Right circular cone of radius \"r\" and height \"h\":");
			P("    Volume = pi*r^2*h/3");
			P("    Base surface area = pi*r^2    Side surface area = pi*r*(r^2 + h^2)^.5\n");

			P("Convex polygon of \"n\" sides, sum of all interior angles formula:");
			P("    Sum = (n - 2)*180 degrees     Sum = (n - 2)*pi radians");
			return true;
		}
		if (strncasecmp(cp, "expressions", cp1 - cp) == 0 || strncasecmp(cp, "equations", cp1 - cp) == 0) {
			P("To enter an expression or equation, simply type it in at the prompt.");
			P("Operators have precedence decreasing as indicated:");
			P("    - negate");
			P("    ! factorial (gamma function)");
			P("    ** or ^ power (exponentiation)");
			P("    * multiply        / divide          % modulus");
			P("    + add             - subtract");
			P("    = equate");
			P("Operators in the same precedence level are evaluated left to right.\n");

			P("Variables consist of any combination of letters, digits, and underscores (_).");
			P("Predefined constants and variables follow:");
			P("    e or e# - the universal constant e (2.7182818284...).");
			P("    pi or pi# - the universal constant pi (3.1415926535...).");
			P("    i or i# - the imaginary number (square root of -1).");
			P("The above constants may also be used anywhere variables are required.");
			P("    sign, sign1, sign2, sign3, ... - variable that can only be +1 or -1.");
			P("    integer - variable that may be any integer.");
			P("    inf - infinity constant.\n");

			P("Absolute value notation \"|x|\" and dual polarity \"+/-\" are understood.");
			return true;
		}
		if (is_all(cp)) {
			for (i = 0, row = 1; i < ARR_CNT(com_list); i++) {
				row += display_command(i);
				if (gfp == stdout && screen_rows && row >= (screen_rows - 5)) {
					row = 1;
					if (!pause_cmd(""))
						return false;
					printf("\n");
				}
			}
			P("End of command list.");
			return true;
		}
	}
	/* default help text: */
	P("This help command is provided as a quick reference.");
	P("Full documentation is available at the website \"www.mathomatic.org\".");
	P("Type \"help all\" for a summary and syntax of the all commands.");
	P("Type \"help usage\" for syntax of all commands.");
	P("Type \"help expressions\" for help with entering expressions.");
	P("Type \"help geometry\" for some commonly used geometric formulas.");
	P("\"help\" or \"?\" followed by command names will give info on those commands.\n");

	P("Available commands:");
	for (i = 0; i < ARR_CNT(com_list); i++) {
		if ((i % 5) == 0)
			fprintf(gfp, "\n");
		j = 15 - fprintf(gfp, "%s", com_list[i].name);
		for (; j > 0; j--)
			fprintf(gfp, " ");
	}

	P("\n\nTo select an equation in memory, type the equation number at the main prompt.");
	P("To solve the current equation, type the variable name at the main prompt.");
	return true;
}


syntax highlighted by Code2HTML, v. 0.9.1