/************************************************************************/
/*                                                                      */
/* Copyright (c) 1988 -                                                 */
/*     The Rector and Visitors of the University of Virginia            */
/*                                                                      */
/* This code may not be distributed further without permission          */
/*      from the University of Virginia.                                */
/* This code is distributed WITHOUT ANY WARRANTY.  No claims are        */
/*      made as to whether it serves any particular purpose or even     */
/*      works at all.                                                   */
/*                                                                      */
/************************************************************************/

#include "types.h"
#include "tword.h"
#include "size.h"
#include "scan.h"
#include "node.h"
#include "object.h"
#include "stopset.h"
#include "required.h"
#include "error.h"

#define LEFTTORIGHT		/* push parameters left to right */

#define BOOLEOP(x) ((x) <= SY_MAX && set_in((x), boole_op))

/* Possible values for "why" nd_gencode is called */

#define yNOTHING 0
#define yFLOW 1	/* Called for flow control */

#define NOLAB (-1)

/*
 * nd_compile() generates code for a single statement.
 * It optimizes the parse tree, calls the code
 * generator, and frees the parse tree.
 */

nd_compile(p)
	NODE *p;
{
	nd_gencode(nd_optim2(nd_optim1(p)), yNOTHING, NOLAB, NOLAB,
		(int *)0, (int *)0);
	nd_freetree(p);
}

/*
 * nd_truejmp() returns the appropriate jump operator for jumping to a
 * true label.
 */

int
nd_truejmp(op)
	int op;
{
	switch(op) {
	case SY_EQUAL:
		return(ND_JMPEQ);
	case SY_GE:
		return(ND_JMPGE);
	case SY_GT:
		return(ND_JMPGT);
	case SY_LE:
		return(ND_JMPLE);
	case SY_LT:
		return(ND_JMPLT);
	case SY_NE:
		return(ND_JMPNE);
	}
	return(0);
}

/*
 * nd_falsejmp() returns the appropriate jump operator for jumping to a
 * false label.
 */

int
nd_falsejmp(op)
	int op;
{
	switch(op) {
	case SY_EQUAL:
		return(ND_JMPNE);
	case SY_GE:
		return(ND_JMPLT);
	case SY_GT:
		return(ND_JMPLE);
	case SY_LE:
		return(ND_JMPGT);
	case SY_LT:
		return(ND_JMPGE);
	case SY_NE:
		return(ND_JMPEQ);
	}
	return(0);
}

/*
 * nd_push() emits code to push a parameter of simple type.
 * Use C language conventions to allow pascal to call C functions.
 */

nd_push(p, count, size)
	register NODE *p;
	int *count, *size;
{
	if (TW_ISPTR(p->nd_type)) {  /* push address */
		(*size) += SZ_PTR;
		(*count)++;
		c_pusha(p->nd_type);
		return;
	}

	switch (p->nd_type) {  /* push value */
	case TW_CHAR:
	case TW_INT:
		(*size) += SZ_INT;
		(*count)++;
		break;

	case TW_DOUBLE:
		(*size) += SZ_DOUBLE;
		(*count) += SZ_DOUBLE / SZ_INT;
		break;

	default:
		sprintf(err_text, "bad paremeter type in nd_push (0%o)",
			p->nd_type);
		err_msg(E_INTERNAL, E_CURLINE);
	}
	c_pushv(p->nd_type);
}

/*
 * nd_branch() emits code to conditionally branch to a label.
 * If nd_branch is passed a true label (tlabel), it emits code
 * to branch to tlabel when the condition is true.  Otherwise
 * it emits code to branch to the false label (flabel) when the
 * condition is false.
 */

nd_branch(p, tlabel, flabel, count, size)
	register NODE *p;
	int tlabel, flabel, *count, *size;
{

	/* boolean expression -- use nd_gencode() */

	if (BOOLEOP(p->nd_op)) {
		nd_gencode(p, yFLOW, tlabel, flabel, count, size);
	}

	/* constant expression -- just generate a goto */

	else if (p->nd_op == ND_ICON) {
		if (flabel != NOLAB && p->nd.tn.ival == 0)
			c_goto(flabel, ob_level);
		if (tlabel != NOLAB && p->nd.tn.ival != 0)
			c_goto(tlabel, ob_level);
	}

	/* expression that computes 0|1 -- compare with 0 and jump */

	else { 
		nd_gencode(p, yNOTHING, tlabel, flabel, count, size);
		c_icon(p->nd_type, SC_NULL, 0);
		if (tlabel != NOLAB)
			c_jmp(ND_JMPNE, p->nd_type, tlabel);
		else
			c_jmp(ND_JMPEQ, p->nd_type, flabel);
	}
}

/*
 * nd_gencode() is the code generation workhorse.  It walks the
 * parse tree and calls functions in the  machine dependent code 
 * generator.
 */

nd_gencode (p, why, tlabel, flabel, count, size)
	register NODE *p;
	int why, tlabel, flabel, *count, *size;
{
	register int opc;
	register NODE *l, *r;
	int nArgs, Argsize;
	int tmplab1, tmplab2;
	int functional;
	OBJECT *ob;

	opc = p->nd_op;

	if (!ND_LEAF(opc)) {
		l = p->nd.in.left;
		r = p->nd.in.right;
	}
		
	switch (opc) {

	case ND_ADDRESS:   /* emit address of object */
		ob = p->nd_object;
		c_address(TW_DECREF(p->nd_type), ob->ob_name,
			nd_sclass(ob), ob->ob_level);
		break;

	case ND_RTFUNC:   /* emit address of run time function */
		c_address(p->nd_type, p->nd.tn.runtime, SC_EXTERN, 0);
		break;

	case ND_ICON:     /* emit a constant */
		if (p->nd_type == TW_DOUBLE)
			c_fcon(p->nd_type, SC_NULL, p->nd.tn.rval);
		else
			c_icon(p->nd_type, SC_NULL, p->nd.tn.ival);
		break;

	case SY_LABEL:    /* emit a label (ptr to string const) */
		c_icon(p->nd_type, SC_LABEL, p->nd.tn.ival);
		break;

	case SY_ASSIGN:   /* assignment operator */
		nd_gencode(r, yNOTHING, tlabel, flabel, count, size);
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		c_assign(p->nd_type);
		break;

	case ND_DEREF:    /* dereference an address */
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		c_deref(p->nd_type);
		break;

	case ND_CALL:     /* function or procedure call */
		nArgs = 0;
		Argsize = 0;
		c_setc();

		/* right branch contains parameters (if any) */

		if (r) {
			nd_gencode (r, yNOTHING, tlabel, flabel, &nArgs, &Argsize);
			if ((r->nd_op != ND_STARG) && (r->nd_op != SY_COMMA ))
				nd_push(r, &nArgs, &Argsize);  /* only 1 parm */
		}

		/*
		 * left branch contains function address.  Usually the left
		 * branch operator is ND_ADDRESS, a normal function call.
		 * If it is ND_DEREF, we're calling a functional parameter,
		 * which is a pointer to a struct.  The struct contains the
		 * address of the function and a saved "display" table so the
		 * function  can handle up-level references correctly.  
		 * Before calling the function, we call run time function
		 * RT_swap_disp that swaps the current display with the display
		 * in the struct, and returns the address of the function.
		 * After the function call we call RT_swap_disp again to
		 * swap back the original display.
		 */

		if (functional = l->nd_op == ND_DEREF) {
			/*
			 * save functional parameter OBJECT since we need
			 * to reference it again later.
			 */
			ob = l->nd.in.left->nd_object;
			nd_compile(nd_build(ND_CALL, TW_INCREF(TW_FTN),
				nd_runtime("RT_swap_disp", TW_INCREF(TW_FTN)), l));
		}
		else
			nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		c_call(p->nd_type, nArgs, Argsize);
		if (p->nd_type != TW_UNDEF)
			c_reasg0(p->nd_type);
		if (functional) {
			nd_compile(nd_build(ND_CALL, TW_UNDEF,
				nd_runtime("RT_swap_disp", TW_UNDEF), nd_address(ob)));
		}
		break;

	case SY_COMMA:
		/*
		 * parameter list for calls with > 1 parameter.  The
		 * parameters are strung together with SY_COMMA nodes.
		 * like this:        ,
		 *                  / \
		 *                 ,  parm3
		 *                / \
		 *            parm1  parm2
		 */

#ifdef RIGHTTOLEFT  /* evaluate parameters right to left */

		nd_gencode (r, yNOTHING, tlabel, flabel, count, size);
		if (r->nd_op != ND_STARG)
			nd_push(r, count, size);
		nd_gencode (l , yNOTHING, tlabel, flabel, count, size);
		if ((l->nd_op != ND_STARG) && (l->nd_op != SY_COMMA))
			nd_push(l, count, size);

#endif

#ifdef LEFTTORIGHT  /* evaluate parameters left to right */

		nd_gencode (l , yNOTHING, tlabel, flabel, count, size);
		if ((l->nd_op != ND_STARG) && (l->nd_op != SY_COMMA))
			nd_push(l, count, size);
		nd_gencode (r, yNOTHING, tlabel, flabel, count, size);
		if (r->nd_op != ND_STARG)
			nd_push(r, count, size);
#endif

		break;

	case SY_EQUAL:   /* comparison operators */
	case SY_NE:
	case SY_LT:
	case SY_GT:
	case SY_LE:
	case SY_GE:

		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		nd_gencode(r, yNOTHING, tlabel, flabel, count, size);
		/*
		 * If why == yFLOW, the comparison is for flow control.
		 * If tlabel != NOLAB emit a true branch to tlabel,
		 * otherwise emit a false branch to flabel.
		 * if why != yFLOW, emit code to compute true or false.
		 */
		if (why == yFLOW) {
			if (tlabel != NOLAB)
				c_jmp(nd_truejmp(opc), l->nd_type, tlabel);
			else
				c_jmp(nd_falsejmp(opc), l->nd_type, flabel);
		}
		else
			c_rel(opc, l->nd_type);
		break;

	case ND_CBRANCH:  /* flow control for "if" "while" "repeat" etc. */

		/* r->nd.tn.ival contains label for false branch */

		nd_branch(l, NOLAB, r->nd.tn.ival, count, size);
		break;

	case SY_OR:    /* "or" operator */
		if ( why == yFLOW) {
			tmplab1 = (tlabel == NOLAB) ? lb_new() : tlabel;
			nd_branch(l, tmplab1, NOLAB, count, size);
			nd_branch(r, tlabel, flabel, count, size);
			if (tlabel == NOLAB)
				c_label(tmplab1);
		}
		else {   /* evaluate to true or false */
			tmplab1 = lb_new();  /* expression set true at tmplab1 */
			tmplab2 = lb_new();
			nd_branch(l, tmplab1, NOLAB, count, size);
			nd_branch(r, tmplab1, NOLAB, count, size);
			c_icon(TW_INT, SC_NULL, 0);
			c_use0(TW_INT);
			c_goto(tmplab2, ob_level);
			c_label(tmplab1);
			c_icon(TW_INT, SC_NULL, 1);
			c_reuse0(TW_INT);
			c_label(tmplab2);
			c_reasg0(TW_INT);
		}
		break;

	case SY_AND:   /* "and" operator */
		if ( why == yFLOW) {
			tmplab1 = (flabel == NOLAB) ? lb_new() : flabel;
			nd_branch(l, NOLAB, tmplab1, count, size);
			nd_branch(r, tlabel, flabel, count, size);
			if (flabel == NOLAB)
				c_label(tmplab1);
		}
		else {   /* evaluate to true or false */
			tmplab1 = lb_new();  /* expression set false at tmplab1 */
			tmplab2 = lb_new();
			nd_branch(l, NOLAB, tmplab1, count, size);
			nd_branch(r, NOLAB, tmplab1, count, size);
			c_icon(TW_INT, SC_NULL, 1);
			c_use0(TW_INT);
			c_goto(tmplab2, ob_level);
			c_label(tmplab1);
			c_icon(TW_INT, SC_NULL, 0);
			c_reuse0(TW_INT);
			c_label(tmplab2);
			c_reasg0(TW_INT);
		}
		break;

	case SY_NOT:  /* "not" operator */
		if (why == yFLOW)  /* reverse labels */
			nd_branch(l, flabel, tlabel, count, size);
		else {
			tmplab1 = lb_new();   /* expression set true at tmplab1 */
			tmplab2 = lb_new();
			nd_branch(l, NOLAB, tmplab1, count, size);
			c_icon(TW_INT, SC_NULL, 0);
			c_use0(TW_INT);
			c_goto(tmplab2, ob_level);
			c_label(tmplab1);
			c_icon(TW_INT, SC_NULL, 1);
			c_reuse0(TW_INT);
			c_label(tmplab2);
			c_reasg0(TW_INT);
		}
		break;


	case SY_PLUS:   /* dyadic arithmetic operators */
	case SY_MINUS:
	case SY_STAR:
	case SY_DIV:
	case SY_SLASH:
	case SY_MOD:
	case ND_LSHIFT:
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		nd_gencode(r, yNOTHING, tlabel, flabel, count, size);
		c_aoper(opc, p->nd_type);
		break;

	case ND_PCONV:  /* pointer to pointer conversion */
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		c_pconv(l->nd_type, p->nd_type);
		break;

	case ND_SCONV:  /* type coercion */
		nd_gencode (l, why, tlabel, flabel, count, size);
		if (l->nd_type != p->nd_type && p->nd_type != TW_UNDEF) 
			c_sconv(l->nd_type, p->nd_type);
		break;

	case ND_ABS:   /* monadic arithmetic operators */
	case ND_ODD:
	case ND_ROUND:
	case ND_SQR:
	case ND_TRUNC:
	case ND_UMINUS:
		nd_gencode (l, why, tlabel, flabel, count, size);
		c_aoper(opc, l->nd_type);
		break;

	case ND_STASG:   /* assignment of structured data types */
		nd_gencode(r, yNOTHING, tlabel, flabel, count, size);
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		ob = p->nd_object;
		nd_compile(p_typesize(ob));
		c_stasg(p->nd_type);
		break; 

	case ND_STARG:   /* pass structured data type as value parameter */
		{
			int nBytes;
			nBytes = ALIGN(p->nd_object->ob_size, AL_INT);
			nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
			(*count) += nBytes/SZ_INT;
			(*size) += nBytes;
			c_starg(p->nd_type, nBytes);
		}
		break; 

	case ND_RANGE:   /* range check ordinal value */
		nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
		l = r->nd.in.left;  /* lower bound */
		r = r->nd.in.right; /* upper bound */
		if (l->nd_op == ND_ICON)
			c_ckrange3(p->nd_type, l->nd.tn.ival, r->nd.tn.ival);
		else {
			nd_gencode(l, yNOTHING, tlabel, flabel, count, size);
			nd_gencode(r, yNOTHING, tlabel, flabel, count, size);
			c_ckrange1(p->nd_type);
		}
		break;

	default:
		sprintf(err_text, "unknown operator in nd_gencode() %d\n", opc);
		err_msg(E_INTERNAL, E_CURLINE);
	}
}
