/* asexpr.c */

/*
 * (C) Copyright 1989-1995
 * All Rights Reserved
 *
 * Alan R. Baldwin
 * 721 Berkeley St.
 * Kent, Ohio  44240
 */

#include <stdio.h>
#include <setjmp.h>
#include <string.h>
#include <alloc.h>
#include "asm.h"

/*)Module	asexpr.c
 *
 *	The module asexpr.c contains the routines to evaluate
 *	arithmetic/numerical expressions.  The functions in
 *	asexpr.c perform a recursive evaluation of the arithmetic
 *	expression read from the assembler-source text line.
 *	The expression may include binary/unary operators, brackets,
 *	symbols, labels, and constants in hexadecimal, decimal, octal
 *	and binary.  Arithmetic operations are prioritized and
 *	evaluated by normal arithmetic conventions.
 *
 *	asexpr.c contains the following functions:
 *		VOID	abscheck()
 *		addr_t	absexpr()
 *		VOID	clrexpr()
 *		int	digit()
 *		VOID	expr()
 *		int	oprio()
 *		VOID	term()
 *
 *	asexpr.c contains no local/static variables
 */

/*)Function	VOID	expr(esp, n)
 *
 *		expr *	esp		pointer to an expr structure
 *		int	n		a firewall priority; all top
 *					level calls (from the user)
 *					should be made with n set to 0.
 *
 *	The function expr() evaluates an expression and
 *	stores its value and relocation information into
 *	the expr structure supplied by the user.
 *
 *	local variables:
 *		int	c		current assembler-source
 *					text character
 *		int	p		current operator priority
 *		area *	ap		pointer to an area structure
 *		exp	re		internal expr structure
 *
 *	global variables:
 *		char	ctype[]		array of character types, one per
 *					ASCII character
 *
 *	functions called:
 *		VOID	abscheck()	asexpr.c
 *		VOID	clrexpr()	asexpr.c
 *		VOID	expr()		asexpr.c
 *		int	getnb()		aslex.c
 *		int	oprio()		asexpr.c
 *		VOID	qerr()		assubr.c
 *		VOID	rerr()		assubr.c
 *		VOID	term()		asexpr.c
 *		VOID	unget()		aslex.c
 *
 *
 *	side effects:
 *		An expression is evaluated modifying the user supplied
 *		expr structure, a sym structure maybe created for an
 *		undefined symbol, and the parse of the expression may
 *		terminate if a 'q' error occurs.
 */

VOID
expr(esp, n)
register struct expr *esp;
int n;
{
        register c, d, p;
        struct area *ap;
        struct expr re;

        term(esp);
        while (ctype[c = getnb()] & BINOP) {
		/*
		 * Handle binary operators + - * / & | % ^ << >>
		 */
                if ((p = oprio(c)) <= n)
                        break;
                if ((c == '>' || c == '<') && c != get())
                        qerr();
		clrexpr(&re);
                expr(&re, p);
		esp->e_rlcf |= re.e_rlcf;
                if (c == '+') {
			/*
			 * esp + re, at least one must be absolute
			 */
                        if (esp->e_base.e_ap == NULL) {
				/*
				 * esp is absolute (constant),
				 * use area from re
				 */
                                esp->e_base.e_ap = re.e_base.e_ap;
                        } else
                        if (re.e_base.e_ap) {
				/*
				 * re should be absolute (constant)
				 */
                                rerr();
                        }
                        if (esp->e_flag && re.e_flag)
                                rerr();
                        if (re.e_flag)
                                esp->e_flag = 1;
                        esp->e_addr += re.e_addr;
                } else
                if (c == '-') {
			/*
			 * esp - re
			 */
                        if ((ap = re.e_base.e_ap) != NULL) {
                                if (esp->e_base.e_ap == ap) {
                                        esp->e_base.e_ap = NULL;
                                } else {
                                        rerr();
                                }
                        }
                        if (re.e_flag)
                                rerr();
                        esp->e_addr -= re.e_addr;
                } else {
			/*
			 * Both operands (esp and re) must be constants
			 */
                        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;

			default:
				qerr();
				break;
                        }
                }
        }
        unget(c);
}

/*)Function	addr_t	absexpr()
 *
 *	The function absexpr() evaluates an expression, verifies it
 *	is absolute (i.e. not position dependent or relocatable), and
 *	returns its value.
 *
 *	local variables:
 *		expr	e		expr structure
 *
 *	global variables:
 *		none
 *
 *	functions called:
 *		VOID	abscheck()	asexpr.c
 *		VOID	clrexpr()	asexpr.c
 *		VOID	expr()		asexpr.c
 *
 *	side effects:
 *		If the expression is not absolute then
 *		a 'r' error is reported.
 */

addr_t
absexpr()
{
        struct expr e;

	clrexpr(&e);
	expr(&e, 0);
	abscheck(&e);
	return (e.e_addr);
}

/*)Function	VOID	term(esp)
 *
 *		expr *	esp		pointer to an expr structure
 *
 *	The function term() evaluates a single constant
 *	or symbol value prefaced by any unary operator
 *	( +, -, ~, ', ", >, or < ).  This routine is also
 *	responsible for setting the relocation type to symbol
 *	based (e.flag != 0) on global references.
 *
 *	local variables:
 *		int	c		current character
 *		char	id[]		symbol name
 *		char *	jp		pointer to assembler-source text
 *		int	n		constant evaluation running sum
 *		int	r		current evaluation radix
 *		sym *	sp		pointer to a sym structure
 *		tsym *	tp		pointer to a tsym structure
 *		int	v		current digit evaluation
 *
 *	global variables:
 *		char	ctype[]		array of character types, one per
 *					ASCII character
 *		sym *	symp		pointer to a symbol structure
 *
 *	functions called:
 *		VOID	abscheck()	asexpr.c
 *		int	digit()		asexpr.c
 *		VOID	err()		assubr.c
 *		VOID	expr()		asexpr.c
 *		int	is_abs()	asexpr.c
 *		int	get()		aslex.c
 *		VOID	getid()		aslex.c
 *		int	getmap()	aslex.c
 *		int	getnb()		aslex.c
 *		sym *	lookup()	assym.c
 *		VOID	qerr()		assubr.c
 *		VOID	unget()		aslex.c
 *
 *	side effects:
 *		An arithmetic term is evaluated, a symbol structure
 *		may be created, term evaluation may be terminated
 *		by a 'q' error.
 */

VOID
term(esp)
register struct expr *esp;
{
        register c, n;
        register char *jp;
        char id[NCPS];
        struct sym  *sp;
        struct tsym *tp;
        int r, v;

        c = getnb();
	/*
 	 * Discard the unary '+' at this point and
	 * also any reference to numerical arguments
	 * associated with the '#' prefix.
	 */
        while (c == '+' || c == '#') { c = getnb(); }
	/*
 	 * Evaluate all binary operators
	 * by recursively calling expr().
	 */
        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_addr = getmap(-1)&0377;
                return;
        }
        if (c == '\"') {
                esp->e_mode = S_USER;
                if (hilo) {
                    esp->e_addr  = (getmap(-1)&0377)<<8;
                    esp->e_addr |= (getmap(-1)&0377);
                } else {
                    esp->e_addr  = (getmap(-1)&0377);
                    esp->e_addr |= (getmap(-1)&0377)<<8;
                }
                return;
        }
        if (c == '>' || c == '<') {
                expr(esp, 100);
		if (is_abs (esp)) {
			/*
			 * evaluate msb/lsb directly
			 */
			if (c == '>')
				esp->e_addr >>= 8;
			esp->e_addr &= 0377;
			return;
		} else {
			/*
			 * let linker perform msb/lsb, lsb is default
			 */
			esp->e_rlcf |= R_BYT2;
			if (c == '>')
				esp->e_rlcf |= R_MSB;
			return;
		}
        }
	/*
	 * Evaluate digit sequences as local symbols
	 * if followed by a '$' or as constants.
	 */
        if (ctype[c] & DIGIT) {
                esp->e_mode = S_USER;
                jp = ip;
                while (ctype[*jp] & RAD10) {
                        jp++;
                }
                if (*jp == '$') {
                        n = 0;
                        while ((v = digit(c, 10)) >= 0) {
                                n = 10*n + v;
                                c = get();
                        }
                        tp = symp->s_tsym;
                        while (tp) {
                                if (n == tp->t_num) {
                                        esp->e_base.e_ap = tp->t_area;
                                        esp->e_addr = tp->t_addr;
                                        return;
                                }
                                tp = tp->t_lnk;
                        }
                        err('u');
                        return;
                }
                r = radix;
                if (c == '0') {
                        c = get();
                        switch (c) {
                                case 'b':
                                case 'B':
                                        r = 2;
                                        c = get();
                                        break;
                                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;
                while ((v = digit(c, r)) >= 0) {
                        n = r*n + v;
                        c = get();
                }
                unget(c);
                esp->e_addr = n;
                return;
        }
	/*
	 * Evaluate '$' sequences as a temporary radix
	 * if followed by a '%', '&', '#', or '$'.
	 */
        if (c == '$') {
                c = get();
                if (c == '%' || c == '&' || c == '#' || c == '$') {
	                switch (c) {
	        	        case '%':
        		                r = 2;
        		                break;
        		        case '&':
        		                r = 8;
        		                break;
        		        case '#':
        		                r = 10;
        		                break;
        		        case '$':
        		                r = 16;        	                
        		                break;
        		        default:
        		                break;
        	        }
        	        c = get();
        	        n = 0;
        	        while ((v = digit(c, r)) >= 0) {
        	                n = r*n + v;
        	                c = get();
        	        }
        	        unget(c);
	                esp->e_mode = S_USER;
        	        esp->e_addr = n;
        	        return;
        	}
        	unget(c);
        	c = '$';
        }
	/*
	 * Evaluate symbols and labels
	 */
        if (ctype[c] & LETTER) {
                esp->e_mode = S_USER;
                getid(id, c);
                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');
                } else {
                        esp->e_mode = sp->s_type;
                        esp->e_addr = sp->s_addr;
                        esp->e_base.e_ap = sp->s_area;
                }
                return;
        }
	/*
	 * Else not a term.
	 */
        qerr();
}

/*)Function	int	digit(c, r)
 *
 *		int	c		digit character
 *		int	r		current radix
 *
 *	The function digit() returns the value of c
 *	in the current radix r.  If the c value is not
 *	a number of the current radix then a -1 is returned.
 *
 *	local variables:
 *		none
 *
 *	global variables:
 *		char	ctype[]		array of character types, one per
 *					ASCII character
 *
 *	functions called:
 *		none
 *
 *	side effects:
 *		none
 */

int
digit(c, r)
register c, r;
{
        if (r == 16) {
                if (ctype[c] & RAD16) {
                        if (c >= 'A' && c <= 'F')
                                return (c - 'A' + 10);
                        if (c >= 'a' && c <= 'f')
                                return (c - 'a' + 10);
                        return (c - '0');
                }
        } else
        if (r == 10) {
                if (ctype[c] & RAD10)
                        return (c - '0');
        } else
        if (r == 8) {
                if (ctype[c] & RAD8)
                        return (c - '0');
        } else
        if (r == 2) {
                if (ctype[c] & RAD2)
                        return (c - '0');
        }
        return (-1);
}

/*)Function	VOID	abscheck(esp)
 *
 *		expr *	esp		pointer to an expr structure
 *
 *	The function abscheck() tests the evaluation of an
 *	expression to verify it is absolute.  If the evaluation
 *	is relocatable then an 'r' error is noted and the expression
 *	made absolute.
 *
 *	Note:	The area type (i.e. ABS) is not checked because
 *		the linker can be told to explicitly relocate an
 *		absolute area.
 *
 *	local variables:
 *		none
 *
 *	global variables:
 *		none
 *
 *	functions called:
 *		VOID	rerr()		assubr.c
 *
 *	side effects:
 *		The expression may be changed to absolute and the
 *		'r' error invoked.
 */

VOID
abscheck(esp)
register struct expr *esp;
{
        if (esp->e_flag || esp->e_base.e_ap) {
                esp->e_flag = 0;
                esp->e_base.e_ap = NULL;
                rerr();
        }
}

/*)Function	int	is_abs(esp)
 *
 *		expr *	esp		pointer to an expr structure
 *
 *	The function is_abs() tests the evaluation of an
 *	expression to verify it is absolute.  If the evaluation
 *	is absolute then 1 is returned, else 0 is returned.
 *
 *	Note:	The area type (i.e. ABS) is not checked because
 *		the linker can be told to explicitly relocate an
 *		absolute area.
 *
 *	local variables:
 *		none
 *
 *	global variables:
 *		none
 *
 *	functions called:
 *		none
 *
 *	side effects:
 *		none
 */

int
is_abs (esp)
register struct expr *esp;
{
        if (esp->e_flag || esp->e_base.e_ap) {
		return(0);
        }
	return(1);
}

/*)Function	int	oprio(c)
 *
 *		int	c		operator character
 *
 *	The function oprio() returns a relative priority
 *	for all valid unary and binary operators.
 *
 *	local variables:
 *		none
 *
 *	global variables:
 *		none
 *
 *	functions called:
 *		none
 *
 *	side effects:
 *		none
 */
 
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);
}

/*)Function	VOID	clrexpr(esp)
 *
 *		expr *	esp		pointer to expression structure
 *
 *	The function clrexpr() clears the expression structure.
 *
 *	local variables:
 *		none
 *
 *	global variables:
 *		none
 *
 *	functions called:
 *		none
 *
 *	side effects:
 *		expression structure cleared.
 */
 
VOID
clrexpr(esp)
register struct expr *esp;
{
	esp->e_mode = 0;
	esp->e_flag = 0;
	esp->e_addr = 0;
	esp->e_base.e_ap = NULL;
	esp->e_rlcf = 0;
}
