
/*
   This software is copyright 1989 by John Dunning.  See the file
   'COPYLEFT.JRD' for the full copyright notice.
*/

/* expression evaluator */

#include "eval.h"
#include "symtab.h"
#include "global.h"

char tbuf[32];

char * eval_internal();		/* forward decl */

int digit(c)
char c;
{

  if ((c >= '0') && (c <= '9'))
	return(c - '0');
    else
  if ((c >= 'A') && (c <= 'F'))
	return(c - 'A' + 10);
    else
	return(-1);
}

char * grok_fixnum(str, base, result)
char * str;
int base;
int * result;
{
  int d;
  char * s;
  int neg;

  s = str;
  *result = 0;
  neg = 0;

  if (*s == '-')
	{
	neg = 1;
	s++;
	}
  while (((d = digit(toupper(*s))) >= 0) && (d <= base))
	{
	*result = *result * base + d;
	s++;
	}
  if (neg)
	{
	*result = -(*result);
	}
  return(s);
}
  
/* eval one q, return updated string ptr */
char * eval_q(str, val, expr_flags, sym_ref)
char * str;
int * val;
int * expr_flags;
struct sym ** sym_ref;
{
  char c;
  char * p;
  struct sym * sy;
  int i;

  while((c = *str) && white_p(c))
	str++;
  if (id_start_p(c))
	{
/* symbol */
	p = tbuf;
	*p++ = *str++;
	while(id_char_p(*str))
		*p++ = *str++;
	*p = '\0';
	if ((tbuf[0] == '.') && (tbuf[1] == '\0'))
		*val = pc;
	    else
		{
		sy = find_sym(tbuf, 1);
		if ((!(sy->flags & DEFINED)) && (pass == 1))
			barf("undefined symbol '%s'", tbuf);
		if (!(sy->flags & DEFINED))
			*expr_flags |= E_UNDEF;
		if (!(sy->flags & ABS))
			*expr_flags |= E_REL;
		*sym_ref = sy;
		*val = sy->value;
		}
	}
    else
  if (c == '$')		/* hex constant */
	str = grok_fixnum(str + 1, 16, val);
    else
  if (c == '0')		/* octal constant */
	str = grok_fixnum(str, 8, val);
    else
  if (c == '@')		/* octal constant */
	str = grok_fixnum(str + 1, 8, val);
    else
  if (c == '%')		/* binary constant */
	str = grok_fixnum(str + 1, 2, val);
    else
  if (c == '^')		/* constant with radix */
	{
	switch(str[1])
		{
		case 'o': { str = grok_fixnum(str + 2, 8, val); break; };
		case 'x': { str = grok_fixnum(str + 2, 16, val); break; };
		case 'd': { str = grok_fixnum(str + 2, 10, val); break; };
		}
	}
    else
  if (((c > '0') && (c <= '9')) || (c == '-'))	/* decimal constant */
	str = grok_fixnum(str, 10, val);
    else
  if (c == '\'')
	{
	*val = str[1];
	str += 2;
	}
    else
  if (c == '*')
	{
	*val = pc;
	str++;
	}
    else
  if (c == '<')
	str = eval_internal(str + 1, val, expr_flags, sym_ref);
    else
  barf("eval error: bogus operand '%c'", c);

  return(str);
}

char * binop(str, accum, expr_flags, sym_ref)
char * str;
int * accum;
int * expr_flags;
struct sym ** sym_ref;
{
  int val;
  char c;

  c = *str++;
  str = eval_q(str, &val, expr_flags, sym_ref);
  switch (c)
	{
	case '+': { *accum += val; break; }
	case '-': { *accum -= val; break; }
	case '*': { *accum *= val; break; }
	case '/': { *accum /= val; break; }
	case '&': { *accum &= val; break; }
	case '|':
	case '!': { *accum |= val; break; }
	}
  return(str);
}

char * eval_internal(str, val, expr_flags, sym_ref)
char * str;
int * val;
int * expr_flags;
struct sym ** sym_ref;
{
  int accum;

  str = eval_q(str, &accum, expr_flags, sym_ref);
  while (white_p(*str))
	str++;
  while (*str)				/* more expr? */
	switch(*str)
		{
		case '+': case '-': case '*': case '/':
		case '&': case '|': case '!':
			{
			str = binop(str, &accum, expr_flags, sym_ref);
			break;
			}
		case '\\':
			{
			(*expr_flags) |= E_LO_BYTE;
			accum &= 0xFF;
			str++;
			break;
			}
		case '^':
			{
			(*expr_flags) |= E_HI_BYTE;
			accum >>= 8;
			str++;
			break;
			}
		case '>':		/* end recursive eval */
			{
			*val = accum;
			return(++str);			
			}
		default: str++;
		};
  *val = accum;
  return(str);
}

int eval(str, expr_flags, sym_ref)
char * str;
int  * expr_flags;
struct sym ** sym_ref;
{
  int accum;

  *expr_flags = 0;
  *sym_ref = (struct sym *)0;
  eval_internal(str, &accum, expr_flags, sym_ref);

#ifdef DEBUG
  printf("eval('%s')->%X flags %X sym %X\n", str, accum, *expr_flags, *sym_ref);
#endif
  return(accum);
}
