/*
 *				e v a l
 *
 * Read an expression and evaluate it.  Prints the value in decimal,
 * octal, and hex.  Any C expression (with parentheses) may be
 * evaluated.  Uses code developed for the C preprocessor.
 *
 * Define "MAIN" to include the driver program.
 */

#define		MAIN


/*)LIBRARY
*/

/*)BUILD	$(TKBOPTIONS) = {
			TASK	= ...EVL
		}
*/
		
#ifdef	DOCUMENTATION

title	eval	Expression Evaluation
index		Evaluate a C expression

synopsis

	 eval expression (as a main program)

	 int
	 eval(string, result)
	 char	*string;	/* String to evaluate	*/
	 union {		/* Pointer to result	*/
	  long	value;		/* Result if no error	*/
	  char	*error;		/* Message if error	*/
	 } *result;

description

	By setting a compile-time parameter, eval may be configured
	as a main program that reads an expression, printing the
	result in decimal, octal, and hexadecimal.

	When compiled as a subroutine, eval() accepts a character
	string argument, returning a long result (and an error indicator):

	If eval() returns zero, the evaluation is stored
	in result.value.  Else, there is an error and and the value returned
	is the index in string of the first byte not parsed, while
	result.error is a pointer to an error message.  (Note that,
	if the error was discovered after the entire string was parsed,
	the index may be greater than the length of the string.)

author

	Martin Minow, using an algorithm developed by
	Mike Lutz and Bob Harper of RPI.

#endif

#define	EOS	0
#define	TRUE	1
#define	FALSE	0
/*
 * If the main program source is removed, check that the #include <setjmp.h>
 * at the start of eval() is reenabled as needed.
 */
#include <setjmp.h>

#ifdef	MAIN

/*
 * This is compiled to create the eval main program.
 */

#include	<stdio.h>
#ifdef	vms
#include	<signal.h>
static int	ctrlc_typed = FALSE;
static jmp_buf	environment;
#endif

static char	line[513];
static int	prompting;

main(argc, argv)
int		argc;
char		*argv[];
{
	register char		*linep;
	register char		*argp;

#ifdef	vms
	extern int		ctrlc_trap();

	signal(SIGINT, ctrlc_trap);
#endif
	if (argc <= 1) {
#ifdef	vms
		if (setjmp(environment) == 0)
		    while (!ctrlc_typed) {
#else
		for (;;) {
#endif
			if (isatty(fileno(stdin))) {
				printf("* ");
				fflush(stdout);
			}
#ifdef	vms
			if (gets(line) == NULL || ctrlc_typed)
				break;
#else
			if (gets(line) == NULL)
				break;
#endif
			else if (line[0] == '?') {
				help();
			}
			else {
				process();
			}
		}
	}
	else {
		if (argv[1][0] == '?' || streq(argv[1], "-?") != FALSE) {
			help();
		}
		else {
			linep = line;
			while (argc > 1) {
				argp = *++argv;
				while (*argp != EOS)
					*linep++ = *argp++;
				*linep++ = ' ';
				argc--;
			}
			*linep = EOS;
			process();
		}
	}
}

static
process()
/*
 * Do the work -- argument is in line.
 */
{
	int		code;
	union {
		long	value;
		char	*message;
	} result;

	if ((code = eval(line, &result)) != 0) {
		printf("?%s in \"%s\"\n", result.message, line);
		if (code >= strlen(line)) {
			printf("Stopped after reading entire line\n");
		}
		else {
			printf("Stopped at byte %d, \"%s\" not evaluated\n",
				code, &line[code]);
		}
		printf("\"%s?\" for help\n", (prompting) ? "" : "eval ");
	}
	else if (result.value == 0)
		printf("0\t0\t0\n");
	else {
		printf("%ld\t0%lo\t0x%lx\n",
			result.value, result.value, result.value);
		if (result.value < 0) {
			printf("\tunsigned: %lu long", result.value);
			if ((result.value & ~65535L) == ~65535L)
				printf(", %lu short", result.value & 65535L);
			if ((result.value &   ~255L) ==   ~255L)
				printf(", %lu char",  result.value &   255L);
			printf("\n");
		}
	}
}

static
help()
/*
 * Give help
 */
{
	printf("Evaluate an integer expression.  Standard C syntax\n");
	printf("is accepted, including parentheses.  The result is\n");
	printf("output in decimal, octal, and hexadecimal.  When a\n");
	printf("number is entered, a leading '0' means octal while\n");
	printf("a leading '0x' or '0X' means hexadecimal.\n");
}

#ifdef	vms
static
ctrlc_trap()
{
	ctrlc_typed = TRUE;
	longjmp(environment, TRUE);
}
#endif
#endif

/*
 *	Constant expression evaluator -- performs a standard recursive
 *	descent parse to evaluate any legal C constant expression.  All
 *	operators without side-effects are implemented (thus ++, --, and all
 *	assignment operators are omitted).  
 *
 *	This code was originally written by Mike Lutz and was modified by
 *	Bob Harper to fit into this program and to conform to C language
 *	standards.
 */

/*
 * This is defined at the start of the file so setjmp can be used in
 * the main program.
 * #include <setjmp.h>
 */

#define	EOS	0
#define	TRUE	1
#define FALSE	0
#define	EQL	0
#define	NEQ	1
#define	LSS	2
#define	LEQ	3
#define	GTR	4
#define	GEQ	5

static char	*nxtch  = EOS;		/* Parser scan pointer		*/
static char	*errmsg;		/* Gets error message		*/
static jmp_buf 	env;			/* Setjmp/longjmp save area	*/

extern long	query(), lor(), land(), bor(), band(), bxor();
extern long	eql(), relat(), shift(), primary(), term(), unary();
extern long	factor(), const(), number(), experr();

/*	<eval> ::= <query> <end-of-string>	*/


int
eval(string, result)
char		*string;	/* Argument				*/
union {
	long	value;
	char	*message;
} *result;
/*
 * Evaluate driver
 */
{
	int		i;
	long		evaluate();

	nxtch = string;
	if (setjmp(env) == 0) {
		result->value = evaluate();
		return(0);
	}
	else {
		result->message = errmsg;
		i = nxtch - string;
		return ((i <= 0) ? 1 : i);
	}
}

static long
evaluate()
{
	long		rval;

	rval = query();
	if (skipws() == EOS)
		return(rval);
	experr("Ill-formed expression");
}

/*	<query> ::= <lor> | <lor> '?' <query> ':' <query>	*/

static long
query()
{
	long		bool, true_val, false_val;

	bool = lor();
	if (skipws() != '?') {
		ungetch();
		return(bool);
	}

	true_val = query();
	if (skipws() != ':')
		experr("Bad query syntax (':' not found)");

	false_val = query();
	return((bool != 0) ? true_val : false_val);
}

/*	<lor> ::= <land> { '||' <land> }	*/

static long
lor()
{
	register int	c;
	long		vl, vr;

	vl = land();
	while ((c = skipws()) == '|' && getch() == '|') {
		vr = land();
#ifdef	decus
		vl = (vl != 0 || vr != 0) ? 1 : 0;
#else
		vl = vl || vr;
#endif
	}
	if (c == '|')
		ungetch();
	ungetch();
	return(vl);
}

/*	<land> ::= <bor> { '&&' <bor> }		*/

static long
land()
{
	register int	c;
	long		vl, vr;

	vl = bor();
	while ((c = skipws()) == '&' && getch() == '&') {
		vr = bor();
#ifdef	decus
		vl = (vl != 0 && vr != 0) ? 1 : 0;
#else
		vl = vl && vr;
#endif
	}
	if (c == '&')
		ungetch();
	ungetch();
	return(vl);
}

/*	<bor> ::= <bxor> { '|' <bxor> }		*/

static long
bor()
{
	register int	c;
	long		vl, vr;

	vl = bxor();
	while ((c = skipws()) == '|' && getch() != '|') {
		ungetch();
		vr = bxor();
		vl |= vr;
	}

	if (c == '|')
		ungetch();
	ungetch();
	return(vl);
}

/*	<bxor> ::= <band> { '^' <band> }	*/

static long
bxor()
{
	long		vl, vr;

	vl = band();
	while (skipws() == '^') {
		vr = band();
		vl = vl ^ vr;
	}

	ungetch();
	return(vl);
}

/*	<band> ::= <eql> { '&' <eql> }	*/

static long
band()
{
	register int	c;
	long		vl, vr;

	vl = eql();
	while ((c = skipws()) == '&' && getch() != '&') {
		ungetch();
		vr = eql();
		vl &= vr;
	}

	if (c == '&')
		ungetch();
	ungetch();
	return(vl);
}

/*	<eql> ::= <relat> { <eqrel> <relat> }	*/

static long
eql()
{
	register int	rel;
	long		vl, vr;

	vl = relat();
	while ((rel = geteql()) != -1) {
		vr = relat();

		switch (rel) {

		case EQL:
			vl = (vl == vr);
			break;
		case NEQ:
			vl = (vl != vr);
			break;
		}
	}
	return(vl);
}

/*	<relat> ::= <shift> { <rel> <shift> }	*/

static long
relat()
{
	register int	rel;
	long		vl, vr;

	vl = shift();
	while ((rel = getrel()) != -1) {

		vr = shift();
		switch (rel) {

		case LEQ:
			vl = (vl <= vr);
			break;
		case LSS:
			vl = (vl < vr);
			break;
		case GTR:
			vl = (vl > vr);
			break;
		case GEQ:
			vl = (vl >= vr);
			break;
		}
	}
	return(vl);
}

/*	<shift> ::= <primary> { <shop> <primary> }	*/

static long
shift()
{
	register int	c;
	long		vl, vr;

	vl = primary();
	while (((c = skipws()) == '<' || c == '>') && c == getch()) {
		vr = primary();

		if (c == '<')
			vl <<= vr;
		else
			vl >>= vr;
	}

	if (c == '<' || c == '>')
		ungetch();
	ungetch();
	return(vl);
}

/*	<primary> ::= <term> { <addop> <term> }		*/

static long
primary()
{
	register int	c;
	long		vl, vr;

	vl = term();
	while ((c = skipws()) == '+' || c == '-') {
		vr = term();
		if (c == '+')
			vl += vr;
		else
			vl -= vr;
	}

	ungetch();
	return(vl);
}

/*	<term> := <unary> { <mulop> <unary> }	*/

static long
term()
{
	register int	c;
	long		vl, vr;

	vl = unary();
	while ((c = skipws()) == '*' || c == '/' || c == '%') {
		vr = unary();

		switch (c) {
			case '*':
				vl *= vr;
				break;
			case '/':
				if (vr == 0)
					experr("Division by zero");
				else	vl /= vr;
				break;
			case '%':
				if (vr == 0)
					experr("Modulus by zero");
				else	vl %= vr;
				break;
		}
	}
	ungetch();
	return(vl);
}

/*	<unary> ::= <factor> | <unop> <unary>	*/

static long
unary()
{
	register int	c;
	long		val;

	if ((c = skipws()) == '!' || c == '~' || c == '-') {
		val = unary();

		switch (c) {
		case '!':	return((val == 0) ? 1 : 0);
		case '~':	return(~ val);
		case '-':	return(- val);
		}
	}

	ungetch();
	return(factor());
}

/*	<factor> ::= <num> | '(' <query> ')'	*/

static long
factor()
{
	long		val;

	if (skipws() == '(') {
		val = query();
		if (skipws() != ')')
			experr("Missing right parenthesis");
		return(val);
	}

	ungetch();
	return(const());
}

/*	<const> ::= <num> | '<char>'	*/

static long
const()
{
/*
 * Note: const() handles multi-byte constants
 */

	register int	i;
	register int	val;
	register char	c;
	int		v[sizeof (int)];


	if (skipws() != '\'') {
		ungetch();
		return(number());
	}
	/*
	 * Multi-byte character constant
	 */
	for (i = 0; i < sizeof(int); i++) {
		if ((c = getch()) == '\'') {
			ungetch();
			break;
		}
		if (c == '\\') {
			switch (c = getch()) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				ungetch();
				c = octal_number();
				break;
			case 'n':
				c = 012;
				break;
			case 'r':
				c = 015;
				break;
			case 't':
				c = 011;
				break;
			case 'b':
				c = 010;
				break;
			case 'f':
				c = 014;
				break;
			}
		}
		v[i] = c;
	}
	if (i == 0 || getch() != '\'')
		experr("Illegal character constant (missing \"'\")");
	for (val = 0; --i >= 0;) {
		val <<= 8;
		val += v[i];
	}
	return(val);
}

/*	<octal_number> ::= <digit [0-7]> | <octal_number> <digit>	*/

static int
octal_number()
/*
 * Note: returns an integer -- called only for '\xxx' constants.
 */
{
	register int	c;
	register int	val;
	register int	ndigits;

	c = skipws();
	val = 0;
	ndigits = 0;
	while (c >= '0' && c <= '7') {
		val *= 8;
		val += (c - '0');
		c = getch();
		ndigits++;
	}
	ungetch();
	if (ndigits <= 0)
		experr("Bad '\\xxx' constant");
	else
		return (val);
}

/*	<number> ::= <digit> | <number> <digit>	*/

static long
number()
{
	register int	c;
	long		val;
	register int	ndigits;
	
	c = skipws();
	val = 0;
	ndigits = 0;
	if (c != '0') {
		while (c >= '0' && c <= '9') {
			val *= 10;
			val += (c - '0');
			c = getch();
			ndigits++;
		}
	}
	else {
		ndigits++;
		if ((c = tolower(getch())) == 'x') {
			for (;(c = tolower(getch())) >= '0' &&
					(c <= '9' || (c >= 'a' && c <= 'z'));
					ndigits++) {
				val *= 16;
				val += ((c >= 'a') ? c - 'a' + 10 : c - '0');
			}
		}
		else {
			while (c >= '0' && c <= '7') {
				val *= 8;
				val += (c - '0');
				ndigits++;
				c = getch();
			}
		}
	}
	ungetch();
	if (ndigits <= 0)
		experr("Numeric constant expected");
	else
		return(val);
}

/*	<eqlrel> ::= '=' | '==' | '!='	*/

static int
geteql()
{
	register int c1, c2;

	c1 = skipws(); c2 = getch();

	switch (c1) {

	case '=':
		if (c2 != '=')
			ungetch();
		return(EQL);

	case '!':
		if (c2 == '=')
			return(NEQ);
		ungetch(); ungetch();
		return(-1);

	default:
		ungetch(); ungetch();
		return(-1);
	}
}


/*	<rel> ::= '<' | '>' | '<=' | '>='	*/

static int
getrel()
{
	register int c1, c2;

	c1 = skipws();
	c2 = getch();

	switch (c1) {

	case '<':
		if (c2 == '=')
			return(LEQ);
		ungetch();
		return(LSS);

	case '>':
		if (c2 == '=')
			return(GEQ);
		ungetch();
		return(GTR);

	default:
		ungetch(); ungetch();
		return(-1);
	}
}

/*	return next character from the expression string.	*/

static int
getch()
{
	return(*nxtch++);
}

/*	Put back the last character examined.	*/

static int
ungetch()
{
	return(*--nxtch);
}

/*	Skip over any white space and return terminating char.	*/

static int
skipws()
{
	register char c;

	while ((c = getch()) <= ' ' && c != EOS)
		;
	return(c);
}

/* 
 * Error handler - sets error flag and exits evaluation
 */

static long
experr(msg)
char *msg;
{
	errmsg = msg;
	longjmp(env, 1);
}
