/* asexpr.c */

/*
 * (C) Copyright 1989
 * All Rights Reserved
 *
 * Alan R. Baldwin
 * 721 Berkeley St.
 * Kent, Ohio  44240
 */

#include <stdio.h>
#include <setjmp.h>
#include "asm.h"

/*
 * Read an expression. The value of the
 * expression and its associated relocation
 * information is stored into the `expr'
 * structure supplied by the user. `N' is
 * a firewall priority; all top level calls
 * (from the user) should be made with `n'
 * set to 0.
 */
VOID
expr(esp, n)
register struct expr *esp;
{
	register c, d, p;
	struct area *ap;
	struct expr re;

	term(esp);
	while (ctype[c = getnb()] == BINOP) {
		if ((p = oprio(c)) <= n)
			break;
		if ((c == '>' || c == '<') && c != get())
			qerr();
		expr(&re, p);
		if (c == '+') {
			if (esp->e_base.e_ap == NULL) {
				esp->e_flag = re.e_flag;
				esp->e_base.e_ap = re.e_base.e_ap;
			} else
			if (re.e_base.e_ap) {
				rerr();
			}
			esp->e_addr += re.e_addr;
		} else
		if (c == '-') {
			if (ap = re.e_base.e_ap) {
				if (esp->e_base.e_ap == ap) {
					esp->e_base.e_ap = NULL;
				} else {
					rerr();
				}
				if (esp->e_flag || re.e_flag) {
					rerr();
				}
			}
			esp->e_addr -= re.e_addr;
		} else {
			abscheck(esp);
			abscheck(&re);
			switch (c) {

			case '*':
				esp->e_addr *= re.e_addr;
				break;

			case '/':
				esp->e_addr /= re.e_addr;
				break;

			case '&':
				esp->e_addr &= re.e_addr;
				break;

			case '|':
				esp->e_addr |= re.e_addr;
				break;

			case '%':
				esp->e_addr %= re.e_addr;
				break;

			case '^':
				esp->e_addr ^= re.e_addr;
				break;

			case '<':
				esp->e_addr <<= re.e_addr;
				break;

			case '>':
				esp->e_addr >>= re.e_addr;
				break;
			}
		}
	}
	unget(c);
}

/*
 * Read an absolute expression.
 */
addr_t
absexpr()
{
	struct expr e;

	expr(&e, 0);
	abscheck(&e);
	return (e.e_addr);
}

/*
 * Read a term.
 * Handles unary operators, brackets,
 * constants in decimal, octal or hexadecimal
 * and identifiers. This routine is also
 * responsible for setting the relocation type
 * to symbol based (e.flag != 0) on global
 * references.
 */
VOID
term(esp)
register struct expr *esp;
{
	register c, n, nd;
	char id[NCPS];
	struct sym  *sp;
	struct tsym *tp;
	int r, v;

	c = getnb();
	if (c == '#') { c = getnb(); }
	if (c == LFTERM) {
		expr(esp, 0);
		if (getnb() != RTTERM)
			qerr();
		return;
	}
	if (c == '-') {
		expr(esp, 100);
		abscheck(esp);
		esp->e_addr = -esp->e_addr;
		return;
	}
	if (c == '~') {
		expr(esp, 100);
		abscheck(esp);
		esp->e_addr = ~esp->e_addr;
		return;
	}
	if (c == '\'') {
		esp->e_mode = S_USER;
		esp->e_flag = 0;
		esp->e_base.e_ap = NULL;
		esp->e_addr = getmap(-1);
		return;
	}
	if (c == '\"') {
		esp->e_mode = S_USER;
		esp->e_flag = 0;
		esp->e_base.e_ap = NULL;
		if (hilo) {
		    esp->e_addr = (getmap(-1)&0377)<<8 | (getmap(-1)&0377);
		} else {
		    esp->e_addr = (getmap(-1)&0377) | (getmap(-1)&0377)<<8;
		}
		return;
	}
	if (c == '>' || c == '<') {
		expr(esp, 100);
		abscheck(esp);
		if (c == '>')
			esp->e_addr >>= 8;
		esp->e_addr &= 0377;
		return;
	}
	if (ctype[c] == DIGIT) {
		esp->e_mode = S_USER;
		esp->e_flag = 0;
		esp->e_base.e_ap = NULL;
		r = radix;
		if (c == '0') {
			c = get();
			switch (c) {
			case 'b':
			case 'B':
				r = 2;
				c = get();
				break;
			case '@':
			case 'o':
			case 'O':
			case 'q':
			case 'Q':
				r = 8;
				c = get();
				break;
			case 'd':
			case 'D':
				r = 10;
				c = get();
				break;
			case 'h':
			case 'H':
			case 'x':
			case 'X':
				r = 16;
				c = get();
				break;
			default:
				break;
			}
		}
		n = 0;
		nd = 0;
		while ((v = digit(c, r)) >= 0) {
			n = r*n + v;
			nd = 10*nd + v;
			c = get();
		}
		if (c=='$') {
			tp = symp->s_tsym;
			while (tp) {
				if (nd == tp->t_num) {
					esp->e_base.e_ap = tp->t_area;
					esp->e_addr = tp->t_addr;
					return;
				}
				tp = tp->t_lnk;
			}
			err('u');
			esp->e_addr = 0;
			return;
		}
		unget(c);
		esp->e_addr = n;
		return;
	}
	if (ctype[c] == LETTER) {
		esp->e_mode = S_USER;
		esp->e_flag = 0;
		esp->e_base.e_ap = NULL;
		esp->e_addr = 0;
		getid(id, c);
		if (sp = lookup(id)) {
			if (sp->s_type == S_NEW) {
				if (sp->s_flag&S_GBL) {
					esp->e_flag = 1;
					esp->e_base.e_sp = sp;
					return;
				}
				err('u');
				return;
			}
			esp->e_mode = sp->s_type;
			esp->e_base.e_ap = sp->s_area;
			esp->e_addr = sp->s_addr;
			return;
		}
		err('u');
		return;
	}
	qerr();
}

/*
 * If `c' is a legal radix `r' digit
 * return its value; otherwise return
 * -1.
 */
int
digit(c, r)
register c, r;
{
	if (r == 16) {
		if (c >= 'A' && c <= 'F')
			return (c - 'A' + 10);
		if (c >= 'a' && c <= 'f')
			return (c - 'a' + 10);
	}
	if (c >= '0' && c <= '9')
		return (c - '0');
	return (-1);
}

/*
 * Check if the value of the supplied
 * expression is absolute; if not give
 * a relocation error and force the
 * type to absolute.
 */
VOID
abscheck(esp)
register struct expr *esp;
{
	if (esp->e_flag || esp->e_base.e_ap) {
		rerr();
		esp->e_flag = 0;
		esp->e_base.e_ap = NULL;
	}
}

/*
 * Return the priority of the binary
 * operator `c'.
 */
int
oprio(c)
register c;
{
	if (c == '*' || c == '/' || c == '%')
		return (10);
	if (c == '+' || c == '-')
		return (7);
	if (c == '<' || c == '>')
		return (5);
	if (c == '^')
		return (4);
	if (c == '&')
		return (3);
	if (c == '|')
		return (1);
	return (0);
}
