/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2011 AT&T Intellectual Property          *
*          Copyright (c) 2020-2023 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                  Martijn Dekker <martijn@inlv.org>                   *
*                                                                      *
***********************************************************************/
/*
 * G. S. Fowler
 * D. G. Korn
 * AT&T Bell Laboratories
 *
 * long integer arithmetic expression evaluator
 * long constants may be represented as:
 *
 *	0ooo		octal
 *	0[xX]hhh	hexadecimal
 *	ddd		decimal
 *	n#ccc		base n, 2 <= b <= 36
 *
 * NOTE: all operands are evaluated as both the parse
 *	 and evaluation are done on the fly
 */

#include <ast.h>
#include <ctype.h>

#define getchr(ex)	(*(ex)->nextchr++)
#define peekchr(ex)	(*(ex)->nextchr)
#define ungetchr(ex)	((ex)->nextchr--)

#define err(ex,msg)	return seterror(ex,msg)

typedef struct				/* expression handle		*/
{
	char*		nextchr;	/* next expression char		*/
	char*		errchr;		/* next char after error	*/
	char*		errmsg;		/* error message text		*/
	long		(*convert)(const char*, char**, void*);
	void*		handle;		/* user convert handle		*/
} Expr_t;

/*
 * set error message string
 */

static long
seterror(Expr_t* ex, char* msg)
{
	if (!ex->errmsg) ex->errmsg = msg;
	ex->errchr = ex->nextchr;
	ex->nextchr = "";
	return 0;
}

/*   
 * evaluate a subexpression with precedence
 */

static long
expr(Expr_t* ex, int precedence)
{
	int	c;
	long	n;
	long	x;
	char*	pos;
	int	operand = 1;

	while (c = getchr(ex), isspace(c));
	switch (c)
	{
	case 0:
		ungetchr(ex);
		if (!precedence) return 0;
		err(ex, "more tokens expected");
	case '-':
		n = -expr(ex, 13);
		break;
	case '+':
		n = expr(ex, 13);
		break;
	case '!':
		n = !expr(ex, 13);
		break;
	case '~':
		n = ~expr(ex, 13);
		break;
	default:
		ungetchr(ex);
		n = 0;
		operand = 0;
		break;
	}
	for (;;)
	{
		switch (c = getchr(ex))
		{
		case 0:
			goto done;
		case ')':
			if (!precedence) err(ex, "too many )'s");
			goto done;
		case '(':
			n = expr(ex, 1);
			if (getchr(ex) != ')')
			{
				ungetchr(ex);
				err(ex, "closing ) expected");
			}
		gotoperand:
			if (operand) err(ex, "operator expected");
			operand = 1;
			continue;
		case '?':
			if (precedence > 1) goto done;
			if (peekchr(ex) == ':')
			{
				getchr(ex);
				x = expr(ex, 2);
				if (!n) n = x;
			}
			else
			{
				x = expr(ex, 2);
				if (getchr(ex) != ':')
				{
					ungetchr(ex);
					err(ex, ": expected for ? operator");
				}
				if (n)
				{
					n = x;
					expr(ex, 2);
				}
				else n = expr(ex, 2);
			}
			break;
		case ':':
			goto done;
		case '|':
			if (peekchr(ex) == '|')
			{
				if (precedence > 2) goto done;
				getchr(ex);
				x = expr(ex, 3);
				n = n || x;
			}
			else
			{
				if (precedence > 4) goto done;
				x = expr(ex, 5);
				n |= x;
			}
			break;
		case '^':
			if (precedence > 5) goto done;
			x = expr(ex, 6);
			n ^= x;
			break;
		case '&':
			if (peekchr(ex) == '&')
			{
				if (precedence > 3) goto done;
				getchr(ex);
				x = expr(ex, 4);
				n = n && x;
			}
			else
			{
				if (precedence > 6) goto done;
				x = expr(ex, 7);
				n &= x;
			}
			break;
		case '=':
		case '!':
			if (peekchr(ex) != '=') err(ex, "operator syntax error");
			if (precedence > 7) goto done;
			getchr(ex);
			x = expr(ex, 8);
			if (c == '=') n = n == x;
			else n = n != x;
			break;
		case '<':
		case '>':
			if (peekchr(ex) == c)
			{
				if (precedence > 9) goto done;
				getchr(ex);
				x = expr(ex, 10);
				if (c == '<') n <<= x;
				else n >>= x;
			}
			else
			{
				if (precedence > 8) goto done;
				if (peekchr(ex) == '=')
				{
					getchr(ex);
					x = expr(ex, 9);
					if (c == '<') n = n <= x;
					else n = n >= x;
				}
				else
				{
					x = expr(ex, 9);
					if (c == '<') n = n < x;
					else n = n > x;
				}
			}
			break;
		case '+':
		case '-':
			if (precedence > 10) goto done;
			x = expr(ex, 11);
			if (c == '+') n +=  x;
			else n -= x;
			break;
		case '*':
		case '/':
		case '%':
			if (precedence > 11) goto done;
			x = expr(ex, 12);
			if (c == '*') n *= x;
			else if (x == 0) err(ex, "divide by zero");
			else if (c == '/') n /= x;
			else n %= x;
			break;
		default:
			if (isspace(c)) continue;
			pos = --ex->nextchr;
			if (isdigit(c)) n = strton(ex->nextchr, &ex->nextchr, NULL, 0);
			else if (ex->convert) n = (*ex->convert)(ex->nextchr, &ex->nextchr, ex->handle);
			if (ex->nextchr == pos) err(ex, "syntax error");
			goto gotoperand;
		}
		if (ex->errmsg) return 0;
		if (!operand) err(ex, "operand expected");
	}
 done:
	ungetchr(ex);
	if (!operand) err(ex, "operand expected");
	return n;
}

/*
 * evaluate an integer arithmetic expression in s
 *
 * (long)(*convert)(const char* string, char** end, void* handle)
 * is a user supplied conversion routine that is called when unknown
 * chars are encountered; string points to the part to be
 * converted and end is adjusted to point to the next non-converted
 * character; if string is 0 then end points to an error message string
 *
 * NOTE: (*convert)() may call strexpr(ex, )
 */

long
strexpr(const char* s, char** end, long(*convert)(const char*, char**, void*), void* handle)
{
	long	n;
	Expr_t	ex;

	ex.nextchr = (char*)s;
	ex.errmsg = 0;
	ex.convert = convert;
	ex.handle = handle;
	n = expr(&ex, 0);
	if (peekchr(&ex) == ':')
		seterror(&ex, "invalid use of :");
	if (ex.errmsg)
	{
		if (convert) (*convert)(NULL, &ex.errmsg, handle);
		ex.nextchr = ex.errchr;
		n = 0;
	}
	if (end) *end = ex.nextchr;
	return n;
}
