/* parse.c */
#include "codegen.h"
#include "ctype.h"
#include "declarations.h"
#include "diagnostics.h"
#include "error.h"
#include "expression.h"
#include "lex.h"
#include "parse.h"
#include "preprocess.h"
#include "stdlib.h"
#include "vars.h"

/*
	Process all input text.
	At this level, only static declarations, defines,
	includes, and function definitions are legal.
*/
void parse()
{
	/* do until no more input */
	while (eof_flg == 0)
	{
		if (amatch("char", 4))
		{
			declstatic(CCHAR);
			ns();
		}
		else if (amatch("int", 3))
		{
			declstatic(CINT);
			ns();
		}
		else if (match("#asm"))
			doasm();
		else if (match("#include"))
			doinclude();
		else if (match("#define"))
			addmac();
		else
			newfunc();
		
		deblank();	/* force eof if pending */
	}
	/** deblank invokes  preprocess(), input_line(), etc. **/
}
/** eo parse **/

/*	>>>>>> start of cc2 <<<<<<<<	*/
/*	Get required array size		*/
/* invoked when declared variable is followed by "[" */
/*	this routine makes subscript the absolute */
/*	size of the array. */
/** doesn't eval expression, nor multi-dimensions. **/

int mk_subscript()
{
	int num[1];

	if (match("]"))
		return 0;			/* null size */
	if (number(num) == 0)			/* go after a number */
	{
		error("must be constant");	/* it isn't */
		num[0] = 1;			/* so force one */
	}
	if (num[0] < 0)
	{
		error("negative size illegal");
		num[0] = (-num[0]);
	}
	needbrack("]");			/* force single dimension */
	return num[0];			/* and return size */
}

/*	Begin a function		*/
/* Called from "parse" this routine tries to make a function */
/*	out of what follows.	*/
/** is_identifier() gets & validates identifier **/

void newfunc()
{
	/* ptr => currfn: */
	char n[NAMESIZE];
	/* max arg stack size: */
	int argtop;

	/** trap for non-function as syntax error, at this point **/
	/** is_identifier() gets & validates identifier **/
	if (is_identifier(n) == 0)  /** might be const **/
	{
		error("illegal function or declaration");
		/* invalidate line: */
		kill();
		return;
	}
	
	/* remember where fn began: */
	fnstart = lineno;
	
	/* note, in function now: */
	infunc = 1;
	
	/* already in symbol table? */
	if (currfn = findstatic(n))
	{
		/* already variable by that name */
		if (currfn[IDENT] != FUNCTION)
			multidef(n);
		/* already function by that name */
		else if (currfn[OFFSET] == FUNCTION)
			multidef(n);
		/* otherwise we have what was earlier assumed to be a function */
		else
			currfn[OFFSET] = FUNCTION;
	}
	/* if not in table, define as a function now */
	else
		currfn = addstatic(n, FUNCTION, CINT, FUNCTION);

	/* Indicate to console what routine that we're in. */
	toconsole();
	outstr("====== ");
	outstr(currfn + NAME);
	outstr("()");
	nl();
	tofile();

	/* we had better see open paren for args... */
	/** formal parameter processing at the beginning
		of a function declaration **/
	if (match("(") == 0)
		error("missing open paren");
	asm_symb(n); colon(); nl();	/* print function name */
	autoptr = startauto;		/* "clear" local symbol table*/
	argstk = 0;				/* init arg count */
	while (match(")") == 0)		/* then count args */
	{
		/* any legal name bumps arg count */
		/** is_identifier() gets & validates identifier **/
		if (is_identifier(n))
		{
			if (findauto(n))
				multidef(n);
			else
			{
				addauto(n,0,0,argstk);
			/* Add local symbol to be later processed by getarg(). */
				argstk = argstk + 2;
			}
		}
		else
		{
			error("illegal argument name");
			junk();
		}
		deblank();

		/* if not closing paren, should be comma */
		if (streq(line+lptr,")") == 0)
		{
			if (match(",") == 0)
				error("expected comma");
		}
		if (endst())
			break;
	}
	
	/* Save max arg stack size: */
	argtop = argstk;
	
	/* now let user declare what types: */
	while (argstk)
	{
		/* of things those arguments were */
		if (amatch("char",4))
		{
			getarg(CCHAR,argtop);
			ns();
			continue;
		}
		else if (amatch("int",3))
		{
			getarg(CINT,argtop);
			ns();
			continue;
		}
		else
		{
			error("wrong number args");
			break;
		}
	}
	
	Msp = 00;	/* Preset local stack ptr. */
	Csp = 00;	/* preset stack ptr */
	FN_Prolog();	/* Do function initialization. */
	
	/* AutoC size is undetermined here, not till statement() is processed **/
	
	/* Do all statements, if last one is a return, skip cleaning up the stack */
	if (statement() != ST_RETURN)
	{
		sa_rtnstk(0);
		sa_ret();
	}

	/* Handle function termination. */
	FN_Epilog();			/* Do function termination. */
	Msp = Csp = 0;			/* reset stack ptr again */
	dumpautos();			/* Dump the local symbol table. */
	autoptr = startauto;		/* deallocate all locals */
	dumplits();			/* Dump the literal pool for the function.*/
	litlab = getlabel();		/* Now re-initialize the pool. */
	litptr = 0;
	infunc = 0;			/* not in fn. any more	gtf 7/2/80 */
} /** eo newfunc **/

/*	Declare argument types		*/
/* called from "newfunc" this routine adds an entry in the */
/*	local symbol table for each named argument */

/* top tells how the max arg stack size was found while
   scanning arguments.  From this, we can calculate the
   the actual stack offset and stuff it back into the
   local symbol table.
*/
/* t = cchar or cint, top = max arg stack */
void getarg(int t, int top)
{
	char n[NAMESIZE], *argptr;
	int j, auto_addr;
	
	while (1)
	{
		/* If no more args */
		if (argstk == 0)
			return;
		
		if (match("*"))
			j = POINTER;
		else
			j = VARIABLE;
		
		/** is_identifier() gets & validates identifier **/
		if (is_identifier(n))
		{
			/* pointer ? */
			if (match("["))
			{
				/* it is a pointer, so skip all stuff between "[]" */
				while (scan_nxt() != ']')
					if (endst())
						break;
				/* add entry as pointer */
				j = POINTER;
			}
			if (argptr = findauto(n))
			{
				argptr[IDENT] = j;
				/* save type info for arg just found. */
				argptr[TYPE] = t;
				auto_addr = top - (argptr[OFFSET+3]<<24)
						- (argptr[OFFSET+2]<<16)
						- (argptr[OFFSET+1]<<8)
						- argptr[OFFSET]+4;	/** was +2 **/

				/*
					Calculate offset to arg taking into account the return
					address and the saved EBP register left on the stack.
				*/
				argptr[OFFSET] = auto_addr;
				argptr[OFFSET+1] = auto_addr>>8;
				argptr[OFFSET+2] = auto_addr>>16;
				argptr[OFFSET+3] = auto_addr>>24;

				/*save offset back in local symbol table entry. */
			}
			else
				error("expecting argument name");
		}
		else
			illname();
		argstk = argstk - 2;		/* cnt down */
		if (endst())
			return;
		if (match(",") == 0)
			error("expected comma");
	}
}

/*	Statement parser		*/
/* called whenever syntax requires	*/
/*	a statement. 			 */
/*  this routine performs that statement */
/*  and returns a number telling which one */

int statement()
{
	if ((inspect_chr() == 0) & (eof_flg))
		return;
	else if (amatch("char", 4))
	{
		declauto(CCHAR);
		ns();
	}
	else if (amatch("int", 3))
	{
		declauto(CINT);
		ns();
	}
	else if (match("{"))
		compound();
	else if (amatch("if", 2))
	{
		doif();
		lastst = ST_IF;
	}
	else if (amatch("do", 2))
	{
		dodo();
		lastst = ST_DO;
	}
	else if (amatch("while", 5))
	{
		dowhile();
		lastst = ST_WHILE;
	}
	else if (amatch("return", 6))
	{
		doreturn();
		ns();
		lastst = ST_RETURN;
	}
	else if (amatch("break", 5))
	{
		dobreak();
		ns();
		lastst = ST_BREAK;
	}
	else if (amatch("continue", 8))
	{
		docont();
		ns();
		lastst = ST_CONT;
	}
/**** -not ready for prime time-
	else if (amatch ("for", 3))
		{
		dofor ();
		lastst = STFOR;
		}
	else if (amatch ("switch", 6))
		{
		doswitch ();
		lastst = STSWITCH;
		}
****/
/***	else if (amatch("case",4))
		{
		docase ();
		lastst = statement ();
		}
	else if (amatch("default", 7))
		{
		dodefault ();
		lastst = statement();
		}
***/
	else if (match(";"))
		;
	else if (match("#asm"))
	{
		doasm();
		lastst = ST_ASM;
	}
	/* if nothing else, assume it's an expression */
	else
	{
						/** presume comma expr possible **/
		expression(YES); 		/** an expression ending with a  **/ 
		ns();				/** semicolon is a statement in C. **/
		lastst = ST_EXPR;
	}
	
	return lastst;
}	/** eo statement **/

/*	Semicolon enforcer	*/
/* called whenever syntax requires a semicolon */
void ns()
{
	if (match(";") == 0)
		error("missing semicolon");
}

/*	Compound statement	*/
/* allow any number of statements to fall between "{}" */
void compound()
{
	++ncmp;					/* new level open */
	while (match("}") == 0) statement();	/* do one */
	--ncmp;					/* close current level */
}

/*	"if" statement	*/
void doif()
{
	int flev, fsp, flab1, flab2;
	int t_lbl;

	flev = autoptr;				/* record current local level */
	fsp = Csp;				/* record current stk ptr */

	t_lbl = getlabel();			/** for true branch **/
	flab1 = getlabel();			/* get label for false branch */

	/**	logical_test(flab1);	* get expression, and branch false */
	logic_tst(t_lbl,flab1); /** test new fn **/
	/**	logical_tst2(flab1,FALSE);	* get expression, and branch false */

	sa_mk_lbl(t_lbl);			/** codegen true branch label **/

	statement();				/* if true, do a statement */
	Csp = sa_modstk(fsp);			/* then clean up the stack */
	autoptr = flev;				/* and deallocate any locals */
	/* if...else ? */
	if (amatch("else", 4) == 0)
	{
		/* simple "if"...print false label */
		sa_mk_lbl(flab1);
		return;
	}

	/* an "if...else" statement. */

	sa_jump(flab2 = getlabel());		/* jump around false code */
	printlabel(flab1); colon(); nl();	/* print false label */
	statement();				/* and do "else" clause */
	Csp = sa_modstk(fsp);			/* then clean up stk ptr */
	autoptr = flev;				/* and deallocate locals */
	sa_mk_lbl(flab2);			/* print true label */
}	/** eo doif **/

/*					*/
/*	"do" statement "while" (expr)	*/
/*					*/
void dodo()
{
	int ws[WS_SZ];				/* allocate local stack */
	int t_lbl;				/** auto 'True' Label **/

	ws[WS_SYMS] = autoptr;			/* record local level */
	ws[WS_SP] = Csp;			/* and stk ptr */

	ws[WS_LBL2] = getlabel();		/* do loop label */
	ws[WS_LOOP] = getlabel();		/* and looping label */
	t_lbl = getlabel();			/** make a unique label for our use **/
	ws[WS_LBL] = getlabel();		/* and exit label */

	addwhile(ws);				/* add entry to stack */

	/* (for "break" statement) */
	sa_mk_lbl(ws[WS_LBL2]);			/* loop label */
	statement();				/* do a statement, body of do_while */
	if (amatch("while", 5) == 0)
	{
		error("'while' expected.");
		return;
	}

	sa_mk_lbl(ws[WS_LOOP]);			/* cont label, at while(expr) */
/**	logical_test(ws[WS_LBL]);	* get expression and branch false */

	logic_do_tst(t_lbl,ws[WS_LBL]); 	/** test new fn, for DO **/
/**	sa_mk_lbl(t_lbl);	** codegen true branch label **/

	sa_jump(ws[WS_LBL2]);			/* continue to loop */

	sa_mk_lbl(ws[WS_LBL]);			/* exit label */

	ns();					/* look for ending semi-colon. */
	autoptr = ws[WS_SYMS];			/* deallocate locals */
	Csp = sa_modstk(ws[WS_SP]);		/* clean up stk ptr */
	delwhile();				/* delete stack entry */
}	/** eo dodo **/

void addwhile(int ptr[])
{
	int k;

	if (wsptr == WS_MAX)
	{
		error("too many active whiles");
		return;
	}
	k = 0;
	while (k < WS_SZ)
	{
		*wsptr++ = ptr[k++];
	}
}

void delwhile()
{
	if (readwhile())
		wsptr = wsptr - WS_SZ;
}

int* readwhile()
{
	if (wsptr == ws)
	{
		error("no active do/for/while/switch");
		return 0;
	}
	else
		return (wsptr - WS_SZ);
}

/*** Additional Statement Support Routines ***/
/*****

findwhile ()
{
	int	*ptr;

	for (ptr = wsptr; ptr != ws;) {
		ptr = ptr - WSSIZ;
		if (ptr[WSTYP] != WSSWITCH)
			return (ptr);
	}
	error ("no active do/for/while");
	return (0);
}

readswitch ()
{
	int	*ptr;

	if (ptr = readwhile ())
		if (ptr[WSTYP] == WSSWITCH)
			return (ptr);
	return (0);
}

addcase (val)
int	val;
{
	int	lab;

	if (swstp == SWSTSZ)
		error ("too many case labels");
	else {
		swstcase[swstp] = val;
		swstlab[swstp++] = lab = getlabel ();
		printlabel (lab);
		col ();
		nl ();
	}
}

/*					*/
/*	"while" (expr) statement	*/
/*					*/

void dowhile()
{
	int ws[WS_SZ];				/* allocate local stack */
	int t_lbl;				/** auto 'True' Label **/

	ws[WS_SYMS] = autoptr;			/* record local level */
	ws[WS_SP] = Csp;			/* and stk ptr */

	ws[WS_LOOP] = getlabel();		/* and looping label */
	/** make a unique label for our use: **/
	t_lbl = getlabel();
	ws[WS_LBL] = getlabel();		/* and exit label */
	addwhile(ws);				/* add entry to stack */

	/* (for "break" statement) */
	sa_mk_lbl(ws[WS_LOOP]);			/* loop label */
/**	logical_test(ws[WS_LBL]);	* see if true */ 
	logic_tst(t_lbl,ws[WS_LBL]);		/** test new fn **/

	sa_mk_lbl(t_lbl);			/** codegen true branch label **/

	statement();				/* if so, do a statement */
	sa_jump(ws[WS_LOOP]);			/* loop to label */
	sa_mk_lbl(ws[WS_LBL]);			/* exit label */
	autoptr = ws[WS_SYMS];			/* deallocate locals */
	Csp = sa_modstk(ws[WS_SP]);		/* clean up stk ptr */
	delwhile();				/* delete stack entry */
}	/** eo dowhile **/

/*
 *	"for" statement
 */
/*****
dofor ()
{
	int	ws[7],
		*pws;

	ws[WSSYM] = autoptr;
	ws[WSSP] = Csp;
	ws[WSTYP] = WSFOR;
	ws[WSTEST] = getlabel ();
	ws[WSINCR] = getlabel ();
	ws[WSBODY] = getlabel ();
	ws[WSEXIT] = getlabel ();
	addwhile (ws);
	pws = readwhile ();
	needbrack ("(");

	if (match (";") == 0) {
		expression (YES);
		ns ();
	}
	gnlabel (pws[WSTEST]);

	if (match (";") == 0) {
		expression (YES);
		testjump (pws[WSBODY], TRUE);
		jump (pws[WSEXIT]);
		ns ();
	} else
		pws[WSTEST] = pws[WSBODY];
	gnlabel (pws[WSINCR]);

	if (match (")") == 0) {
		expression (YES);
		needbrack (")");
		jump (pws[WSTEST]);
	} else
		pws[WSINCR] = pws[WSTEST];
	gnlabel (pws[WSBODY]);
	statement (NO);
	jump (pws[WSINCR]);
	gnlabel (pws[WSEXIT]);
	locptr = pws[WSSYM];
	stkp = modstk (pws[WSSP]);
	delwhile ();
}
*****/

/*
 *	"switch" statement
 */
/*****
doswitch ()
{
	int	ws[7];
	int	*ptr;

	ws[WSSYM] = locptr;
	ws[WSSP] = stkp;
	ws[WSTYP] = WSSWITCH;
	ws[WSCASEP] = swstp;
	ws[WSTAB] = getlabel ();
	ws[WSDEF] = ws[WSEXIT] = getlabel ();
	addwhile (ws);
	immed ();
	printlabel (ws[WSTAB]);
	nl ();
	gpush ();
	needbrack ("(");
	expression (YES);
	needbrack (")");
	stkp = stkp + intsize(); **'?case' will adjust the stack **
	gjcase ();
	statement (NO);
	ptr = readswitch ();
	jump (ptr[WSEXIT]);
	dumpsw (ptr);
	gnlabel (ptr[WSEXIT]);
	locptr = ptr[WSSYM];
	stkp = modstk (ptr[WSSP]);
	swstp = ptr[WSCASEP];
	delwhile ();
}
*****/
/*
 *	"case" label
 */
/*****
docase ()
{
	int	val;

	val = 0;
	if (readswitch ()) {
	
		if (number (&val) == 0)
		
			if (pstr (&val) == 0)
				error ("bad case label");
		addcase (val);
	
		if (match (":") == 0)
			error ("missing colon");
	} else
		error ("no active switch");
}
*****/
/*
 *	"default" label
 */
/*****
dodefault ()
{
	int	*ptr,
		lab;

	if (ptr = readswitch ()) {
		ptr[WSDEF] = lab = getlabel ();
		gnlabel (lab);
	
		if (match (":") == 0)
			error ("missing colon");
	} else
		error ("no active switch");
}
*****/

/*	"return" statement	*/
void doreturn()
{
	/* if not end of statement, get an expression */
	if (endst() == 0)
		expression(YES);
	sa_rtnstk(0);				/* clean up stk */
	sa_ret();				/* and exit function */
}	/** eo doreturn **/

/*	"break" statement		*/
void dobreak()
{
	int *ptr;

	/* see if any "whiles" are open */

	if ((ptr = readwhile()) == 0)
		return;				/* no */
	sa_modstk((ptr[WS_SP]));		/* else clean up stk ptr */
	sa_jump(ptr[WS_LBL]);			/* jump to exit label */
}	/** eo dobreak **/

/*	"continue" statement	*/
void docont()
{
	int *ptr;

	/* see if any "whiles" are open */

	if ((ptr = readwhile()) == 0)
		return;
	sa_modstk((ptr[WS_SP]));		/* else clean up stk ptr */
	sa_jump(ptr[WS_LOOP]);			/* jump to loop label */
}

/*	"asm" pseudo-statement	*/
/* enters mode where assembly language statement are */
/*	passed intact through parser	*/
void doasm()
{
	int a;

	cmode = 0;				/* mark mode as "asm" */
	a = 1;

	while (a == 1)
	{
		/* get and print lines */
		input_line();
		
		if (match("#endasm"))
		{
			sa_comment();
			/** pass back endasm comment **/ 
			outstr(line);
			break;
		}
		
		if (eof_flg)
			break;
		
		outstr(line);
		nl();
	}
	/**	kill();		* invalidate line */
	/* then back to parse level */
	cmode = 1;
}

/*	>>>>> start of cc3 <<<<<<<<<	*/

/*	Perform a function call	*/
/*  called from heir11, this routine will either call	*/
/*	the named function, or if the supplied ptr is		*/
/*	zero, will call the contents of Primary Register	*/
void callfunction(char* ptr)
{
	int nargs;

	nargs = 0;
	deblank();				/* already saw open paren */

/** Primary Register has target address, push PR [1]**/

	if (ptr == 0)
		sa_push();
	
	while (streq(line + lptr, ")") == 0)
	{
		if (endst())
			break;
		
		expression(NO);			/* get an argument */
		if (ptr==0)
			sa_swapstk();		/* don't push addr */
		sa_push();			/* push argument */
		nargs = nargs + INT_SZ;		/* count args*2 */
		if (match(",") == 0)
			break;
	}
	needbrack(")");
	if (ptr)
		sa_call(ptr);
	else
		sa_callstk();
	/** target address is on the stack already [1] **/
	Csp = sa_modstk(Csp + nargs);		/* clean up arguments */
}	/** eo callfunction **/

void junk()
{
	if (an(scan_nxt()))
		while (an(inspect_chr()))
			g_nxtchr();
	else
		while (an(inspect_chr()) == 0)
		{
			if (inspect_chr() == 0)
				break;
			g_nxtchr();
		}
	deblank();
}

int endst()
{
	deblank();
	return ((streq(line + lptr, ";") | (inspect_chr() == 0)));
}

void illname()
{
	error("illegal symbol name");
	junk();
}

void multidef(char* sname)
{
	error("already defined");
	sa_comment();
	outstr(sname); nl();
}

void needbrack(char* str)
{
	if (match(str) == 0)
	{
		error("missing bracket");
		sa_comment();
		outstr(str);
		nl();
	}
}

void needlval()
{
	error("must be lvalue");
}

/** -= Scope =- match sname to static's, secondly **/
char* findstatic(char* sname)
{
	char *ptr;

	ptr = startstatic;
	while (ptr != staticptr)
	{
		if (astreq(sname, ptr, NAMEMAX))
			return ptr;
		ptr = ptr + SYMB_SIZE;
	}
	return 0;
}

/** -= Scope =- match sname to auto's, firstly **/
/** - on match, return ptr to sname in auto's table - **/
char* findauto(char* sname)
{
	char *ptr;

	ptr = startauto;
	while (ptr != autoptr)
	{
		if (astreq(sname,ptr,NAMEMAX))
			return ptr;
		/** matched, return with ptr into tbl **/
		ptr = ptr + SYMB_SIZE;  /** ptr == &sname[0]   **/
	}
	return 0;
}

/* catch previously defined symbol and return its ptr, symbptr, which is
** its address in the static symbol table.  If not previously defined,
** define it now.
*/
char* addstatic(char* sname, char id, char typ, int value)
{
	char *ptr;

	if (symbptr = findstatic(sname))
		return symbptr;
	
	/** trap for no room: **/
	if (staticptr >= endstatics)
	{
		error("global symbol table overflow");
		return 0;
	}
	
	symbptr = ptr = staticptr;
	
	/* copy name: */
	while (an(*ptr++ = *sname++))
		;
	
	symbptr[IDENT] = id;
	symbptr[TYPE] = typ;
	symbptr[STORAGE] = C_STATIC;
	symbptr[OFFSET] = value;
	symbptr[OFFSET+1] = value >> 8;
	symbptr[OFFSET+2] = value >> 16;
	symbptr[OFFSET+3] = value >> 24;
	/** update to next free position: **/
	staticptr = staticptr + SYMB_SIZE;
	return symbptr;
}

char* addauto(char* sname, char id, char typ, int value)
{
	char *ptr;

	if (symbptr = findauto(sname))
		return symbptr;
	
	if (autoptr >= endautos)
	{
		error("local symbol table overflow");
		return 0;
	}
	
	symbptr = ptr = autoptr;
	
	/* copy name: */
	while (an(*ptr++ = *sname++))
		;
	
	symbptr[IDENT] = id;
	symbptr[TYPE] = typ;
	symbptr[STORAGE] = C_AUTO;
	symbptr[OFFSET] = value;
	symbptr[OFFSET+1] = value >> 8;
	symbptr[OFFSET+2] = value >> 16;
	symbptr[OFFSET+3] = value >> 24;
	autoptr = autoptr + SYMB_SIZE;
	return symbptr;
}

/* Test if next input string is legal symbol name */
/** A legal identifier is a chr, perhaps followed by alphanumerics **/
/** is_identifier, token input, ck lexeme in line buffer, returns T..F **/
/** sname[k] limit?? **/
/** ex. sname[NAMESIZE]; **/
/** was symname() -> tok_in() -> is_identifier **/
int is_identifier(char* sname)
{
	/** LEX ck, 1st chr alpha, rest alpha-numeric, append null **/
	int k;
	char c;

	deblank();
	/** 1st check for leading chr for identifier **/
	if (alpha(inspect_chr()) == 0)
		return FALSE;
	
	/** flag lexeme as number ? **/
	
	/** inspect next chr for alph-numeric **/ 
	k = 0;
	/** retrieve identifier from stream **/
	while (an(inspect_chr()))
		sname[k++] = g_nxtchr();
	/** append null: **/
	sname[k] = 0;
	return TRUE;
}

/* Return next avail internal label number */
int getlabel()
{
	return(++nxtlab);
}