/*
 * C compiler
 *
 * This file is part of BKUNIX project, which is distributed
 * under the terms of the GNU General Public License (GPL).
 * See the accompanying file "COPYING" for more details.
 */
#include <stdarg.h>
#include "c0.h"

/*
 * Reduce the degree-of-reference by one.
 * e.g. turn "ptr-to-int" into "int".
 */
int
decref(t)
register int t;
{
	if ((t & ~TYPE) == 0) {
		error("Illegal indirection");
		return(t);
	}
	return ((t>>TYLEN) & ~TYPE) | (t&TYPE);
}

/*
 * Increase the degree of reference by
 * one; e.g. turn "int" to "ptr-to-int".
 */
int
incref(t)
register int t;
{
	return(((t&~TYPE)<<TYLEN) | (t&TYPE) | PTR);
}

/*
 * Make a tree that causes a branch to lbl
 * if the tree's value is non-zero together with the cond.
 */
void
cbranch(t, lbl, cond)
union tree *t;
{
	treeout(t, 0);
	outcode("BNNN", CBRANCH, lbl, cond, line);
}

/*
 * Write out a tree.
 */
void
rcexpr(tp)
register union tree *tp;
{
	/*
	 * Special optimization
	 */
	if (tp->t.op==INIT && tp->t.tr1->t.op==CON) {
		if (tp->t.type==CHAR || tp->t.type==UNCHAR) {
			outcode("B1N0", BDATA, tp->t.tr1->c.value);
			return;
		} else if (tp->t.type==INT || tp->t.type==UNSIGN) {
			outcode("BN", SINIT, tp->t.tr1->c.value);
			return;
		}
	}
	treeout(tp, 0);
	outcode("BN", EXPR, line);
}

void
treeout(tp, isstruct)
register union tree *tp;
{
	register struct nmlist *hp;
	register int nextisstruct;

	if (tp == NULL || tp->t.op==NULLOP) {
		outcode("B", XNULLOP);
		return;
	}
	nextisstruct = tp->t.type==STRUCT;
	switch(tp->t.op) {

	case NAME:
		hp = &tp->t.tr1->n;
		if (hp->hclass==TYPEDEF)
			error("Illegal use of type name");
		outcode("BNN", NAME, hp->hclass==0?STATIC:hp->hclass, tp->t.type);
		if (hp->hclass==EXTERN)
			outcode("S", hp->name);
		else
			outcode("N", hp->hoffset);
		break;

	case LCON:
		outcode("BNNN", tp->l.op, tp->l.type, (unsigned short)(tp->l.lvalue>>16),
		   (unsigned short)tp->l.lvalue);
		break;

	case CON:
		outcode("BNN", tp->c.op, tp->c.type, tp->c.value);
		break;

	case FCON:
		outcode("BNF", tp->f.op, tp->f.type, tp->f.cstr);
		break;

	case STRING:
		outcode("BNNN", NAME, STATIC, tp->t.type, tp->t.tr1);
		break;

	case FSEL:
		treeout(tp->t.tr1, nextisstruct);
		outcode("BNNN", tp->t.op, tp->t.type,
		   tp->t.tr2->fld.bitoffs, tp->t.tr2->fld.flen);
		break;

	case ETYPE:
		error("Illegal use of type");
		break;

	case AMPER:
		treeout(tp->t.tr1, 1);
		outcode("BN", tp->t.op, tp->t.type);
		break;


	case CALL:
		treeout(tp->t.tr1, 1);
		treeout(tp->t.tr2, 0);
		outcode("BN", CALL, tp->t.type);
		break;

	default:
		treeout(tp->t.tr1, nextisstruct);
		if (opdope[tp->t.op]&BINARY)
			treeout(tp->t.tr2, nextisstruct);
		outcode("BN", tp->t.op, tp->t.type);
		break;
	}
	if (nextisstruct && isstruct==0)
		outcode("BNN", STRASG, STRUCT, tp->t.strp->S.ssize);
}

/*
 * Generate a branch
 */
void
branch(lab)
{
	outcode("BN", BRANCH, lab);
}

/*
 * Generate a label
 */
void
label(l)
{
	outcode("BN", LABEL, l);
}

/*
 * ap is a tree node whose type
 * is some kind of pointer; return the size of the object
 * to which the pointer points.
 */
int
plength(p)
register union tree *p;
{
	register int t, l;

	if (p==0 || ((t=p->t.type)&~TYPE) == 0)		/* not a reference */
		return(1);
	p->t.type = decref(t);
	l = length(p);
	p->t.type = t;
	return(l);
}

/*
 * return the number of bytes in the object
 * whose tree node is acs.
 */
int
length(cs)
union tree *cs;
{
	register int t, elsz;
	long n;
	int nd;

	t = cs->t.type;
	n = 1;
	nd = 0;
	while ((t&XTYPE) == ARRAY) {
		t = decref(t);
		n *= cs->t.subsp[nd++];
	}
	if ((t&~TYPE)==FUNC)
		return(0);
	if (t>=PTR)
		elsz = SZPTR;
	else switch(t&TYPE) {

	case VOID:
		error("Illegal use of void object");
		return(2);

	case INT:
	case UNSIGN:
		elsz = SZINT;
		break;

	case CHAR:
	case UNCHAR:
		elsz = 1;
		break;

	case FLOAT:
		elsz = SZFLOAT;
		break;

	case UNLONG:
	case LONG:
		elsz = SZLONG;
		break;

	case DOUBLE:
		elsz = SZDOUB;
		break;

	case STRUCT:
		if ((elsz = cs->t.strp->S.ssize) == 0)
			error("Undefined structure");
		break;
	default:
		error("Compiler error (length)");
		return(0);
	}
	n *= elsz;
	if (n >= (unsigned)50000)
		werror("very large data structure");
	return(n);
}

/*
 * The number of bytes in an object, rounded up to a word.
 */
int
rlength(cs)
union tree *cs;
{
	return((length(cs)+ALIGN) & ~ALIGN);
}

/*
 * After an "if (...) goto", look to see if the transfer
 * is to a simple label.
 */
int
simplegoto()
{
	register struct nmlist *csp;

	if ((peeksym=symbol())==NAME && nextchar()==';') {
		csp = csym;
		if (csp->hblklev == 0)
			csp = pushdecl(csp);
		if (csp->hclass==0 && csp->htype==0) {
			csp->htype = ARRAY;
			csp->hflag |= FLABL;
			if (csp->hoffset==0)
				csp->hoffset = isn++;
		}
		if ((csp->hclass==0||csp->hclass==STATIC)
		 &&  csp->htype==ARRAY) {
			peeksym = -1;
			return(csp->hoffset);
		}
	}
	return(0);
}

/*
 * Return the next non-white-space character
 */
int
nextchar()
{
	while (spnextchar()==' ')
		peekc = 0;
	return(peekc);
}

/*
 * Return the next character, translating all white space
 * to blank and handling line-ends.
 */
int
spnextchar()
{
	register int c;

	if ((c = peekc)==0)
		c = getchar();
	if (c=='\t' || c=='\014')	/* FF */
		c = ' ';
	else if (c=='\n') {
		c = ' ';
		line++;
	}
	peekc = c;
	return(c);
}

/*
 * is a break or continue legal?
 */
void
chconbrk(l)
int l;
{
	if (l==0)
		error("Break/continue error");
}

/*
 * The goto statement.
 */
void
dogoto()
{
	register union tree *np;
	register char *st;

	st = starttree();
	*cp++ = tree(0);
	build(STAR);
	chkw(np = *--cp, -1);
	rcexpr(block(JUMP, 0, (int *)NULL, (union str *)NULL, np, TNULL));
	endtree(st);
}

/*
 * The return statement, which has to convert
 * the returned object to the function's type.
 */
void
doret()
{
	if (nextchar() != ';') {
		register char *st;

		st = starttree();
		*cp++ = (union tree *)&funcblk;
		*cp++ = tree(0);
		build(ASSIGN);
		cp[-1] = cp[-1]->t.tr2;
		build(RFORCE);
		rcexpr(*--cp);
		endtree(st);
	}
	branch(retlab);
}

/*
 * Write a character on the error output.
 */
/*
 * Coded output:
 *   B: beginning of line; an operator
 *   N: a number
 *   S: a symbol (external)
 *   1: number 1
 *   0: number 0
 */
/* VARARGS1 */
void
outcode(char *s, ...)
{
	va_list ap;
	register FILE *bufp;
	register char *np;
	register int n;

	bufp = stdout;
	if (strflg)
		bufp = sbufp;
	va_start (ap, s);
	for (;;) switch(*s++) {
	case 'B':
		fputc(va_arg(ap, int), bufp);
		fputc(0376, bufp);
		continue;

	case 'N':
		n = va_arg(ap, int);
		fputc(n, bufp);
		fputc(n>>8, bufp);
		continue;

	case 'F':
		np = va_arg(ap, char*);
		n = 1000;
		goto str;

	case 'S':
		np = va_arg(ap, char*);
		n = MAXCPS-1;
		if (*np)
			fputc('_', bufp);
	str:
		while(n-- && *np) {
			fputc(*np++ & 0177, bufp);
		}
		fputc(0, bufp);
		continue;

	case '1':
		fputc(1, bufp);
		fputc(0, bufp);
		continue;

	case '0':
		fputc(0, bufp);
		fputc(0, bufp);
		continue;

	case '\0':
		if (ferror(bufp)) {
			error("Write error on temp");
			exit(1);
		}
		va_end (ap);
		return;

	default:
		error("Botch in outcode");
	}
}

unsigned int
hash(sp)
register char *sp;
{
	register unsigned int h;

	h = 0;
	for (; *sp; sp++) {
		h += h;
		h += *sp;
	}
	return(h%HSHSIZ);
}
