/************************************************************************
 *    crsubs.c -- subroutines used by parser.
 ************************************************************************/

# include "crunch.h"

Head_p	hd_case = NULL;	/* List of case's for switch stmt.		*/
Head_p	hd_switch = NULL; /* List of pending hd_case's for nested switches.*/
Head_p	hd_stmt = NULL;	/* List of trees representing code for current	*/
			/* function.					*/
Head_p	hd_init = NULL;	/* Pending initialisation statements to be executed*/
			/* after a variable is declared.		*/
node_t	*main_tree = NULL;/* Tree for function main(). Needed so	we can  */
			/* delay generating code for main() until we've */
			/* parsed all declarations.			*/
Head_p	hd_arglist = NULL;
Head_p	hd_globals = NULL;
			/* List of global declarations needed so we     */
			/* can put initialisation code into _init(main).*/
Head_p	hd_syms = NULL;	/* Linked list of symbol table declarations. 	*/
			/* Ordered so that we can make symbols come and	*/
			/* go as we enter block declarations.		*/
			/* Things declared at innermost level at front	*/
			/* of list.					*/
Head_p	hd_funcs = NULL;/* List of currently defined functions.		*/
Head_p	hd_decls = NULL;
node_t	*hd_nodes;	/* List of node_t's allocated so we can free them*/
			/* after compilation.				*/

Head_p	hd_id = NULL;	/* Stack of identifiers seen but not yet finished*/
			/* processing yet.				*/
int	block_level;	/* Current block nesting level so that we can	*/
			/* mark things in the symbol table.		*/
void	*node_mem;	/* Pointer used by vm_alloc for fast malloc()'s	*/
int	break_level;	/* Keep track of whether break is valid.	*/
int	continue_level;	/* Keep track of whether continue is valid.	*/
long	return_type;	/* Return type of current function.		*/

extern int inside_struct;
extern	Head_p	hd_block, hd_undef;
extern	int	switch_level;
extern char yytext[];
extern symbol_t	*struct_sp;
extern int line_no;
extern char *current_fn;

extern Head_p hd_struct;
/**********************************************************************/
/*   Prototypes							      */
/**********************************************************************/
void	enter_loop_or_switch PROTO((int));
char	*pop_id();
void	push_id();
void	add_stmt();
node_t *	new_node();
node_t *	string_node();
node_t *	node();
node_t *	node_lvalue();
void	init_symtab PROTO((void));
node_t *	alloc_node();
void	free_nodes PROTO((void));
void	free_list PROTO((Head_p *));
void	init_lex PROTO((void));
char *map();

/**********************************************************************/
/*   Initialise  variables  which  are  needed by the crunch parser.  */
/*   May  be  called  multiple  times,  so  only  re-initialise data  */
/*   structures if they are NULL.				      */
/**********************************************************************/
void
init_parser()
{	extern int	loop_top;

	init_lex();
	init_symtab();
	init_codegen();

	loop_top = 0;
	line_no = 1;
	block_level = 0;
	break_level = 0;
	continue_level = 0;
	main_tree = NULL;

	free_nodes();

	free_list(&hd_arglist);
	free_list(&hd_block);
	free_list(&hd_init);
	free_list(&hd_stmt);
	free_list(&hd_undef);

	free_list(&hd_case);
	free_list(&hd_decls);
	free_list(&hd_funcs);
	free_list(&hd_globals);
	free_list(&hd_id);
	free_list(&hd_struct);
	free_list(&hd_switch);

	if (hd_globals == NULL) {	
		hd_globals = ll_init();
		hd_funcs = ll_init();
		}
}
void
add_stmt(hd_stmt, stmt)
Head_p	*hd_stmt;
node_t	*stmt;
{
	if (*hd_stmt == NULL)
		*hd_stmt = ll_init();
	ll_append(*hd_stmt, (char *) stmt);
}
node_t *
new_node()
{	
	node_t *np = (node_t *) vm_alloc(sizeof (node_t), &node_mem);
	static node_t null_node;

	stats.s_new_nodes++;
	*np = null_node;
# if defined(NODE_MAGIC)
	np->magic = NODE_MAGIC;
# endif
	np->lineno = line_no;
# define	REMEMBER(np)	{np->next = hd_nodes; hd_nodes = np; }
	REMEMBER(np);
	return np;
}

/**********************************************************************/
/*   Routine   to   free   all   nodes  allocated  during  the  last  */
/*   compilation.  We  do  this en bulk because it makes the code so  */
/*   much simpler rather than freeing as we go along.		      */
/**********************************************************************/
void
free_nodes()
{	register node_t *np;
	while (hd_nodes) {
		np = hd_nodes->next;
		hd_nodes->next = NULL;
		if (hd_nodes->type == node_symbol || hd_nodes->type == node_string)
			chk_free(hd_nodes->atom.sval);
# if defined(NODE_MAGIC)
		assert(hd_nodes->magic == NODE_MAGIC);
		hd_nodes->magic = NODE_FREE;
# endif
		vm_free((void *) hd_nodes, &node_mem);
		stats.s_new_nodes--;
		assert(stats.s_new_nodes >= 0);
		hd_nodes = np;
		}
}

/**********************************************************************/
/*   Routine  to  free  all  elements  in a list -- just freeing the  */
/*   list  atoms.  We  leave the freeing of the contents of the atom  */
/*   to some other code.					      */
/**********************************************************************/
void
free_list(hdp)
register Head_p	*hdp;
{	register List_p	lp;
	if (*hdp == NULL)
		return;
	while (lp = ll_first(*hdp))
		ll_delete(lp);
	ll_free(*hdp);
	*hdp = NULL;
}

/**********************************************************************/
/*   Allocate  a  node  containing a symbol type and copy the string  */
/*   containing the symbol name.				      */
/**********************************************************************/
node_t *
string_node(str)
char *str;
{	char *cp = (char *) strdup(str);
	node_t	*np = new_node();
	
	np->type = node_symbol;
	np->atom.sval = cp;
	return np;
}
/**********************************************************************/
/*   Same  as  string_node()  above  but  we  already  have a buffer  */
/*   allocated to the string.					      */
/**********************************************************************/
node_t *
string_node1(str)
char *str;
{
	node_t	*np = new_node();
	
	np->type = node_symbol;
	np->atom.sval = str;
	return np;
}

/**********************************************************************/
/*   Create  an  atom  in  the tree containing an integer containing  */
/*   the number passed as argument.				      */
/**********************************************************************/
node_t *
new_number(n)
long	n;
{	
	node_t	*np = new_node();
	
	np->type = node_integer;
	np->atom.ival = n;
	return np;
}

/**********************************************************************/
/*   Create an atom containing a floating point constant.	      */
/**********************************************************************/
node_t *
new_float(n)
double	n;
{	
	node_t	*np = new_node();
	
	np->type = node_float;
	np->atom.floatval = n;
	return np;
}
/**********************************************************************/
/*   Return  a  new  node for the parse tree with 'a' as the type of  */
/*   the node and b and c being the left and right pointers.	      */
/**********************************************************************/
node_t *
node(a,b,c)
long	a;
node_t *b;
node_t *c;
{
	node_t	*np = new_node();
	np->left = b;
	np->right = c;
	np->type = node_keywd;
	np->atom.ival = a;
	return np;
}
/**********************************************************************/
/*   This  function  is  similar to 'node()' but attempts to do some  */
/*   simple constant optimisations.				      */
/**********************************************************************/
node_t *
node_opt(a,b,c)
long	a;
node_t *b;
node_t *c;
{

	/***********************************************/
	/*   Only  optimise  integer  operations  for  */
	/*   the moment.			       */
	/***********************************************/
	if (b == NULL || c == NULL || b->type != node_integer || c->type != node_integer)
		return node(a, b, c);
	switch (a) {
	  case PLUS:
		b->atom.ival += c->atom.ival;
		return b;
	  case MINUS:
		b->atom.ival -= c->atom.ival;
		return b;
	  case MUL:
		b->atom.ival *= c->atom.ival;
		return b;
	  case DIV:
	  	if (c->atom.ival == 0) {
			yywarn("Constant expression divides by zero");
			b->atom.ival = 0;
			}
		else
			b->atom.ival /= c->atom.ival;
		return b;
	  case MOD:
	  	if (c->atom.ival == 0) {
			yywarn("Constant expression modulo zero");
			b->atom.ival = 0;
			}
		else
			b->atom.ival %= c->atom.ival;
		return b;
	  case OR:
		b->atom.ival |= c->atom.ival;
		return b;
	  case XOR:
		b->atom.ival ^= c->atom.ival;
		return b;
	  case AND:
		b->atom.ival &= c->atom.ival;
		return b;
	  default:
		return node(a, b, c);
	  }
	
}
/**********************************************************************/
/*   Function  to  create  a  tree  as  a  result  of  an assignment  */
/*   operator.   We  map  the  "list[expr]  =  ..."  syntax  into  a  */
/*   (put_nth expr list ...) macro.				      */
/**********************************************************************/
node_t *
node_lvalue(op, l, r)
long	op;
register node_t	*l;
node_t	*r;
{
	if (l->type == node_keywd && l->atom.ival == OSQUARE) {
		node_t *np = alloc_node((long) K_NOOP);
		node_t	*np1, *np2;
		
		if (op != EQ) {
			char	buf[80];
			sprintf(buf, "%s operation not supported on array elements", 
				map((int) op));
			yyerror(buf);
			}
		np->left = l->left;
		np->right = r;
		np1 = alloc_node((long) K_NOOP);
		np1->left = l->right;
		np1->right = np;
		np2 = alloc_node((long) K_FUNCALL);
		np2->left = string_node("put_nth");
		np2->right = np1;
		return np2;
		}
	return node(op, l, r); 
}
node_t *
alloc_node(keywd)
long	keywd;
{
	node_t	*np = new_node();
	np->type = node_keywd;
	np->atom.ival = keywd;

	return np;
}
/**********************************************************************/
/*   Function  called  at  start  of  a  function  with  the type of  */
/*   function and the parameter list.				      */
/**********************************************************************/
int
start_function(p1, p2)
node_t	*p1;
node_t	*p2;
{	long	type = (long) p1;

	if (type == 0 || (type & TY_MASK) == TY_IMPLICIT) {
		yywarn("return-value defaults to `int'");
		type = TY_INT;
		}
	current_fn = peek_id();
	return_type = type & TY_MASK;
	return 0;
}
/**********************************************************************/
/*   Simple  function  to  check  the  return  type  of  a  function  */
/*   against  the  return(expr)'  or  'return;'  statement.  We only  */
/*   handle void vs. non-void.					      */
/**********************************************************************/
void
return_value(expr)
int	expr;
{
	if (expr && return_type == TY_VOID) {
		yywarn("return with expression in function returning void");
		return;
		}
	if (!expr && return_type != TY_VOID) {
		yywarn("return with no value");
		return;
		}
}

void
add_decl(np)
node_t	*np;
{	char	*name = pop_id();
	decl_t	*dp;
	symbol_t	*sp;
	
	dp = (decl_t *) ll_elem(ll_first(hd_decls));
	sp = add_symbol(name, np, dp->d_type);
	chk_free((void *) name);
	if (sp) {
		sp->s_susym = struct_sp;
		}
}
long
peek_decl()
{	decl_t *dp = (decl_t *) ll_elem(ll_first(hd_decls));
	return dp->d_type;
}
void
push_decl(type)
long	type;
{	decl_t	*dp = (decl_t *) chk_alloc(sizeof (decl_t));

	dp->d_type = type;
	dp->d_symbol = struct_sp;
	if (hd_decls == NULL)
		hd_decls = ll_init();
	ll_push(hd_decls, (char *) dp);
}
void
pop_decl()
{	decl_t *dp;
	List_p	lp = ll_first(hd_decls);
	
	dp = (decl_t *) ll_elem(lp);

	struct_sp = dp->d_symbol;
	chk_free((void *) dp);
	ll_delete(lp);
}
symbol_t *
defining_struct(name, type)
char	*name;
long	type;
{	struct_t	*stp = (struct_t *) chk_alloc(sizeof(struct struct_t));
	symbol_t	*sp;

	if (hd_struct == NULL)
		hd_struct = ll_init();
	inside_struct++;
	sp = add_symbol(name, (node_t *) NULL, type);
	sp->s_flags |= SF_DEFINING;
	stp->s_symbol = sp;
	stp->s_struct_sp = struct_sp;
	struct_sp = sp;
	ll_push(hd_struct, (char *) stp);
	return sp;
		
}
symbol_t *
ending_struct()
{	struct_t *stp = (struct_t *) ll_elem(ll_first(hd_struct));
	Head_p	hd = NULL;
	List_p	lp, lp1;
	symbol_t	*sp;
	
	sp = stp->s_symbol;
	struct_sp = sp; /*stp->s_struct_sp;*/
	ll_pop(hd_struct);
	sp->s_flags &= ~SF_DEFINING;
	/***********************************************/
	/*   Now   move   all   incomplete  structure  */
	/*   definitions  to  the  beginning  of  the  */
	/*   symbol  table.  (This means that when we  */
	/*   dump  the  symbol  table,  we  calculate  */
	/*   the   size   of   structures  which  are  */
	/*   defined  inside  of other structures and  */
	/*   thus get the structure offsets correct.   */
	/***********************************************/
# if 1
	for (lp = ll_first(hd_syms); lp; lp = lp1) {
		symbol_t *sp = (symbol_t *) ll_elem(lp);
		long	type = sp->s_type & TY_MASK;
		lp1 = ll_next(lp);
		if (sp->s_owner == NULL &&
		    (type == TY_STRUCT || type == TY_UNION) &&
		    sp->s_flags & SF_DEFINING) {
		    	if (hd == NULL)
				hd = ll_init();
		    	ll_append(hd, (char *) sp);
			ll_delete(lp);
		    	}
		}
# endif
	if (hd) {
		while ((lp = ll_first(hd)) != NULL) {
			symbol_t *sp = (symbol_t *) ll_elem(lp);
			ll_push(hd_syms, (char *) sp);
			ll_delete(lp);
			}
		ll_free(hd);
		}
	inside_struct--;
	return sp;
}
void
add_member(np)
node_t *np;
{	char	*name = pop_id();
	decl_t *dp = (decl_t *) ll_elem(ll_first(hd_decls));
	symbol_t	*sp;
	struct_t	*stp;

	stp = (struct_t *) ll_elem(ll_first(hd_struct));
	sp = add_symbol_to_table(&stp->s_symbol->s_members,
		INSERT_AT_END, name, np, dp->d_type,
		struct_sp);
	chk_free((void *) name);
	if (sp)
		sp->s_susym = struct_sp;
}
void
push_id(str)
char	*str;
{
	if (hd_id == NULL)
		hd_id = ll_init();
	ll_push(hd_id, str);
}
char	*
peek_id()
{
	List_p lp = ll_first(hd_id);
	
	assert(lp != NULL);
	return (char *) ll_elem(lp);
}
char	*
pop_id()
{	char	*str;
	List_p lp = ll_first(hd_id);
	
	assert(lp != NULL);
	str = (char *) ll_elem(lp);
	ll_delete(lp);
	return str;
}
void
start_switch()
{	switch_t	*sp = (switch_t *) chk_alloc(sizeof(switch_t));
	switch_level++;
	if (hd_switch == NULL)
		hd_switch = ll_init();
	sp->sw_stmts = hd_stmt;
	sp->sw_cases = hd_case;
	ll_push(hd_switch, (char *) sp);
	hd_case = NULL;
	hd_stmt = NULL;
}
node_t *
end_switch(np)
node_t	*np;
{	node_t	*case_tree = NULL;
	List_p	lp;
	switch_t	*sp;
	Head_p	hd = ll_init();
	
	end_case();
	switch_level--;
	
	/***********************************************/
	/*   Reverse order of cases		       */
	/***********************************************/
	while ((lp = ll_first(hd_case)) != NULL) {
		ll_push(hd, ll_elem(lp));
		ll_delete(lp);
		}
	while ((lp = ll_first(hd)) != NULL) {
		case_tree = node((long) K_NOOP, case_tree, (node_t *) ll_elem(lp));
		ll_delete(lp);
		}
	sp = (switch_t *) ll_elem(ll_first(hd_switch));
	hd_case = sp->sw_cases;
	hd_stmt = sp->sw_stmts;
	ll_pop(hd_switch);
	chk_free((void *) sp);
	return node((long) K_SWITCH, np, case_tree);
}
void
start_case(np)
node_t	*np;
{

	/***********************************************/
	/*   End any previous dangling case	       */
	/***********************************************/
	end_case();
	
	if (hd_case == NULL)
		hd_case = ll_init();
	ll_push(hd_case, (char *) np);
}
void
end_case()
{	node_t	*np;

	/***********************************************/
	/*   Check  for  end  of first case in switch  */
	/*   stmt.				       */
	/***********************************************/
	if (hd_case == NULL)
		return;
	np = (node_t *) ll_elem(ll_first(hd_case));
	
	/***********************************************/
	/*   Now   associate   statements  with  this  */
	/*   case expression.			       */
	/***********************************************/
	np = node((long) K_CASE, np, (node_t *) hd_stmt);
	hd_stmt = NULL;
	ll_pop(hd_case);
	ll_push(hd_case, (char *) np);
}

void
enter_loop()
{
	break_level++;
	continue_level++;
	enter_loop_or_switch(INSIDE_LOOP);
	enter_block();
}
void
exit_loop()
{	block_t	*bp;

	break_level--;
	continue_level--;
	block_level--;
	bp = (block_t *) ll_elem(ll_first(hd_block));
	hd_stmt = bp->b_stmt;
	hd_init = bp->b_init;
	chk_free((void *) bp);
	ll_pop(hd_block);
}

