/* 
   Copyright 1994-2003 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  

   You may contact the author at:

   mailto::camille@bluegrass.net

   or by snail mail at:

   David Lindauer
   850 Washburn Ave Apt 99
   Louisville, KY 40222
*/
#include        <stdio.h>
#include				<string.h>
#include				"lists.h"
#include        "expr.h"
#include        "c.h"
#include				"errors.h"

extern int mainfunc ;
extern int lineno ;
extern int cbautoinithead ;
extern int ispascal, isstdcall;
extern FILE *listFile;
extern enum e_sym lastst;
extern long firstlabel,nextlabel;
extern char lastid[];
extern TABLE *gsyms,lsyms;
extern int global_flag;
extern int stdaddrsize, stdretblocksize;
extern int stackadd,stackmod;
extern TYP *head, *tail,**headptr;
extern char declid[100];
extern int prm_listfile;
extern int prm_cplusplus, prm_debug;
extern int stdintsize;
extern int goodcode;
extern int prm_linkreg;
extern TABLE lsyms;
extern int total_errors;
extern int cppflags;
extern SYM *declclass,*typequal;
int block_nesting,arg_nesting;
int infuncargs,infunclineno ;
ENODE *block_rundown; /* maintains destructors for block, WILL maintain global
											 * scope when no funcs in progress but we don't use that
											 * now
                       */
TABLE oldlsym;
LIST *varlisthead,*varlisttail;
LIST *instantiated_inlines ;
SNODE *funcstmt ;

static int skm_func[] = { closepa,begin,semicolon,0 };

SYM *currentfunc=0;

/*      function compilation routines           */
void funcini(void)
{
	currentfunc = 0;
   infuncargs = 0 ;
}
void declfuncarg(int isint, int isfar, SYM *sp)
{       char    *names[50];             /* 50 parameters maximum */
        int     nparms, poffset, i, isinline;
        SYM     *sp1, *sp2, *spsave[50], *oldargs=0, *typesp=typequal;
				TYP *temp, *temp1,*todo,*oheadptr;
				char oldeclare[100];
				char *nm;
            sp->hasproto = TRUE ;
            if (!infuncargs) {
               if (lineno != infunclineno) 
                  funcstmt = snp_line(FALSE) ; // only want the first one
               infunclineno = lineno ;
            }
            infuncargs++ ;
				if (prm_linkreg && !isint) {
        		poffset = stdretblocksize;            /* size of return block */
						if (isfar)
							poffset += stdintsize ;
				}
				else
        	poffset = 0;            /* size of return block */
        nparms = 0;
				temp = head;
				temp1 = tail;
				oheadptr = headptr;
				todo = sp->tp;
				if (!prm_cplusplus) {
					sp2 = gsearch(declid);
					if (sp2) {
						oldargs = sp2->tp->lst.head;
					}
				}
				todo->lst.head = todo->lst.tail = 0;
				if (lastst == kw_void) {
					getsym();
					if (lastst != closepa) {
						backup(lastst);
						lastst = kw_void;
					}
					else {
						todo->lst.head = (SYM *)-1;
						getsym();
					  goto verify;
					}
				}
				else
					if (lastst == closepa) {
						if (prm_cplusplus)
							todo->lst.head = (SYM *)-1;
						getsym();
						goto verify;
					}
				strcpy(oldeclare,declid);
        if(!prm_cplusplus && lastst == id && 
							((sp1 = search(lastid,gsyms)) ==0 || sp1->storage_class != sc_type) 
							&& ((sp1 = search(lastid,&lsyms)) ==0 || sp1->storage_class != sc_type)) {
              /* declare parameters */
					global_flag = 0;
                while(lastst == id) {
                        names[nparms++] = litlate(lastid);
                        getsym();
                        if( lastst == comma)
                                getsym();
                        else
                                break;
                        }
                needpunc(closepa,skm_func);
                sp->oldstyle = xalloc((nparms+1) * sizeof(char *) ) ;
                sp->oldstyle[0] = nparms ;
                memcpy(&sp->oldstyle[1],names,nparms * sizeof(char *)) ;
					global_flag = 1;
               if (prm_debug)
                  debug_outfunc(sp) ;
               return ;
				}
				else {
					doargdecl(sc_member,names,&nparms,&todo->lst,isinline = TRUE);
					needpunc(closepa,skm_func);
				}
				typequal = typesp;
				strcpy(declid,oldeclare);
            if (!ispascal && isstructured(todo->btp)) {
					poffset += stdaddrsize;
               sp->calleenearret = TRUE ;               
            }
        for(i = 0;i < nparms;++i) {
            if ((sp1 = search(names[i],&todo->lst)) == 0 && !isinline)
              sp1 = makeint(litlate(names[i]),&todo->lst);
						if (sp1->tp)
							sp1->tp->uflags |= UF_DEFINED;
						sp1->funcparm = TRUE;
						spsave[i] = sp1;
            sp1->storage_class = sc_auto;
				}
				/*
				 * parameter allocation.  Have to do things backwards if this
				 * function has the pascal declarator
				 */
				if (!ispascal)
        	for(i = 0;i < nparms;++i) {
						sp1 = spsave[i];
						if( sp1->tp->type != bt_pointer && sp1->tp->size < stdintsize)
						{
							sp1->value.i = poffset + funcvaluesize(sp1->tp->size);
							poffset += stdintsize;
						}
						else
						{
							sp1->value.i = poffset;
							if (sp1->tp->type == bt_pointer)
								poffset += stdaddrsize;
                     else {
								poffset += sp1->tp->size;
                        if (poffset %stdintsize)
                           poffset += stdintsize - poffset % stdintsize ;
                     }
						}
					}
				else
        	for(i = nparms-1;i >=0;--i) {
						sp1 = spsave[i];
						if( sp1->tp->type != bt_pointer && sp1->tp->size < stdintsize)
						{
							sp1->value.i = poffset + funcvaluesize(sp1->tp->size);
							poffset += stdintsize;
						}
						else
						{
							sp1->value.i = poffset;
							if (sp1->tp->type == bt_pointer)
								poffset += stdintsize;
                     else {
								poffset += sp1->tp->size;
                        if (poffset %stdintsize)
                           poffset += stdintsize - poffset % stdintsize ;
                     }
						}
					}
				if (!isinline) {
					todo->lst.head = todo->lst.tail = 0;
					for (i=0; i < nparms; i++)
						insert(spsave[i],&todo->lst);
				}
#ifdef CPLUSPLUS
				if (prm_cplusplus && todo->lst.head == 0)
					todo->lst.head = (SYM *) -1; /* () is equiv to (void) in cpp */
#endif
verify:
				head = temp;
				tail = temp1;
				headptr = oheadptr;
				if (prm_debug)
					debug_outfunc(sp) ;

#ifdef CPLUSPLUS
				if (prm_cplusplus) {
               infuncargs-- ;
					return;
				}
#endif
				
				if (oldargs && head->lst.head) {
					SYM *newargs = head->lst.head;
					while (oldargs && newargs) {
						if (oldargs == (SYM *)-1 || newargs == (SYM *)-1)
							break;
                  if (!exactype(oldargs->tp,newargs->tp))
                     gensymerror(ERR_DECLMISMATCH,sp2->name);
						oldargs = oldargs->next;
						newargs = newargs->next;
					}
					if (oldargs && oldargs != (SYM *)-1)
						gensymerror(ERR_ARGLENSHORT,sp->name);
					if (newargs && newargs != (SYM *)-1)
						gensymerror(ERR_ARGLENLONG,sp->name);
				}
            if (sp2 && !exactype(sp2->tp->btp,head->btp))
					gensymerror(ERR_DECLMISMATCH,sp2->name);
            infuncargs-- ;
}
void check_funcused(TABLE *oldlsym, TABLE *lsyms)
{
	/* oldlsym Must BE 0 at the end of a function */
	SYM *sym1;
	if (oldlsym && oldlsym->head == lsyms->head) {
		return;
	}
	sym1 = lsyms->head;
	while (sym1 && (!oldlsym || sym1 != oldlsym->head)) {
		if (sym1->tp->type != bt_func && sym1->tp->type != bt_ifunc
				&& sym1->storage_class != sc_type) {
			if (!(sym1->tp->uflags & UF_USED)) {
				if (sym1->storage_class == sc_label) {
					if (!oldlsym)
						gensymerror(ERR_UNUSEDLABEL,sym1->name);
				}
				else 
					if (sym1->funcparm)
						gensymerror(ERR_PARAMUNUSED,sym1->name);
					else
						gensymerror(ERR_SYMUNUSED,sym1->name);
			}
			if (sym1->tp->uflags & UF_ASSIGNED)
				gensymerror(ERR_SYMASSIGNED,sym1->name);
			if (!oldlsym && sym1->storage_class == sc_ulabel)
				gensymerror(ERR_UNDEFLABEL,sym1->name);
		}
		sym1 = sym1->next;
	}
}
void dump_instantiates(void)
{
	while (instantiated_inlines) {
		currentfunc = instantiated_inlines->data ;
		instantiated_inlines = instantiated_inlines->link ;
		genfunc(currentfunc->value.classdata.inlinefunc->stmt) ;
		release_local() ;
	}
	currentfunc = 0 ;
}
void funcbody(SYM *sp)
/*
 */
{
            SYM *sp1, *sp2;
				instantiated_inlines = 0 ;
				varlisthead = 0;
				/* 1 if inline, 0 if local define */
        global_flag = !!(sp->value.classdata.cppflags & PF_INLINE);
            /* Have to defer old style argument gathering until now
             * because of the pointer-to func syntax
             */
            if (sp->oldstyle) {
               SYM *spsave[50],*sp1 ;
               int nparms,i ;
               int poffset = stdretblocksize;            /* size of return block */
               char **names ;
               global_flag++ ;
               doargdecl(sc_member,0,0,&sp->tp->lst,FALSE);      /* declare parameters */
               global_flag-- ;
               if (!ispascal && isstructured(sp->tp->btp)) {
                  poffset += stdaddrsize;
                  sp->calleenearret = TRUE ;               
               }
               nparms = (int)sp->oldstyle[0] ;
               names = &sp->oldstyle[1] ;
               for(i = 0;i < nparms;++i) {
                  if ((sp1 = search(names[i],&sp->tp->lst)) == 0)
                     sp1 = makeint(litlate(names[i]),&sp->tp->lst);
                  if (sp1->tp)
                     sp1->tp->uflags |= UF_DEFINED;
                  sp1->funcparm = TRUE;
                  spsave[i] = sp1;
                  sp1->storage_class = sc_auto;
               }
				/*
				 * parameter allocation.  Have to do things backwards if this
				 * function has the pascal declarator
				 */
               for(i = 0;i < nparms;++i) {
                  sp1 = spsave[i];
                  if( sp1->tp->type != bt_pointer && sp1->tp->size < stdintsize)
                  {
                     sp1->value.i = poffset + funcvaluesize(sp1->tp->size);
                     poffset += stdintsize;
                  }
                  else
                  {
                     sp1->value.i = poffset;
                     if (sp1->tp->type == bt_pointer)
                        poffset += stdaddrsize;
                     else {
                        poffset += sp1->tp->size;
                        if (poffset %stdintsize)
                           poffset += stdintsize - poffset % stdintsize ;
                     }
                  }
               }
            }
            sp1 = sp->tp->lst.head ;
				if (sp1 != (SYM *)-1) {
					while (sp1) {
						if (sp1->name[0] != '*') {
							sp2 = copysym(sp1) ;
							insert(sp2,&lsyms);
                     browse_variable(sp2,lineno) ;
						}
						sp1 = sp1->next;
					}
				}
				currentfunc = sp;
        cseg();
				firstlabel = nextlabel;
            if (sp->storage_class == sc_global && !(sp->value.classdata.cppflags & PF_INLINE))
					globaldef(sp);
				goodcode &= ~GF_RETURN;
        block();
				check_funcused(0,&lsyms);
				if (prm_listfile && varlisthead) {
					LIST *q = varlisthead;
      		fprintf(listFile,"\n\n*** local symbol table ***\n\n");
					while (q) {
						SYM *sp = q->data;
						while (sp) {
							list_var(sp,0);
							sp = sp->next;
						}
						q = q->link;
					}
				}
				if (sp->tp->btp->type != bt_void && !(goodcode &GF_RETURN)) 
					generror(ERR_FUNCRETVAL,0,0);
        nl();
				currentfunc = 0;

        release_local();        /* release local symbols */
        release_opt() ;
        release_oc() ;
#ifdef ICODE
        dag_rundown() ;
#endif
        lsyms.head = 0;
        oldlsym.head = oldlsym.tail = 0;

        dump_instantiates() ;

        global_flag = 1;
        while (lastst == semicolon)
         getsym() ;
}

SYM     *makeint(char *name, TABLE *table)
{       SYM     *sp;
        TYP     *tp;
				global_flag++;
        sp = xalloc(sizeof(SYM));
        tp = xalloc(sizeof(TYP));
				global_flag--;
        tp->type = bt_int;
        tp->size = 4;
        tp->btp = tp->lst.head = 0;
        tp->sp = 0;
        sp->name = name;
        tp->bits = tp->startbit = -1;
        sp->storage_class = sc_auto;
        sp->tp = tp;
        insert(sp,table);
        return sp;
}
/* a little ditty to add C++ destructors on the tail of a block
 */
void addrundown(SNODE *snp)
{
	if (block_rundown && snp) {
			SNODE *snp2;
			snp2 = xalloc(sizeof(SNODE));
			snp2->next = 0;
			snp2->stype = st_expr;
			snp2->exp = block_rundown;
			block_rundown = 0;
			while (snp->next)
				snp = snp->next;
			snp->next = snp2;
	}
}
void addblocklist(SYM *sp)
{
	LIST *l;
	if (!sp) return;
	l = xalloc(sizeof(LIST));
	l->link = 0;
	l->data = sp;
	if (varlisthead)
		varlisttail = varlisttail->link = l;
	else
		varlisthead = varlisttail = l;
}
void block(void)
{       
            SNODE *snp3 ;
            SNODE *funcsym = funcstmt ; // in case they declare function ptrs
				ENODE *obrd = block_rundown;
            int checkthunkret = mainfunc && prm_cplusplus ; /* blockdecl resets mainfunc */
				block_rundown = 0;
				block_nesting = 1;
				arg_nesting = 0;
				lastst = 0;
            snp3 = snp_line(TRUE);
				lastst = begin;
				needpunc(begin,0);
				cbautoinithead = 0 ;
				if (prm_debug)
               debug_beginblock(lsyms) ;
            browse_blockstart(lineno) ;
        blockdecl();
				cseg();
            if (prm_cplusplus && (currentfunc->value.classdata.cppflags & PF_CONSTRUCTOR)) {
               SNODE *snp4 = xalloc(sizeof(SNODE)) ;
               snp4->stype = st_expr ;
               snp4->exp = 0 ;
               // thunk flags reset in decl.c before loading initializers
               thunkConstructors(snp4,currentfunc->parentclass,0,FALSE) ;
               RefErrs(currentfunc->parentclass) ;
               if (snp3) {
                  snp3->next = snp4 ;
               } else
                  snp3 = snp4 ;
            }

            if (snp3) {
               SNODE *snp5 = snp3 ;
               while (snp5->next)
                  snp5 = snp5->next ;
               snp5->next = compound();
            } else
               snp3 = compound();

            /* Thunk in a return value of zero if this is the main func
              * and there was no return statement
              */
            if (checkthunkret) {
               SNODE *snp4 = snp3 ;
               while (snp4->next)
                  snp4 = snp4->next ;
               if (snp4->stype != st_return) {
                  snp4->next = xalloc(sizeof(SNODE)) ;
                  snp4 = snp4->next ;
                  snp4->stype = st_return ;
                  snp4->exp = makeintnode(en_icon,0) ;
               }
            }
            /* create destructor call tree for end of function */
            if (prm_cplusplus && (currentfunc->value.classdata.cppflags & PF_DESTRUCTOR)) {
               SNODE snp4 ;
               snp4.stype = st_expr ;
               snp4.exp = 0 ;
               setthunkflags(currentfunc->parentclass,FALSE) ;
               thunkDestructors(&snp4,currentfunc->parentclass,0,FALSE,TRUE) ;
               if (block_rundown)
                  block_rundown = makenode(en_void,(void *)block_rundown,(void *)snp4.exp) ;
               else
                  block_rundown = snp4.exp ;
            }
            currentfunc->value.classdata.eofdest = block_rundown ;
            block_rundown =0 ;
/*            addrundown(snp3); */
				addblocklist(lsyms.head);
				if (!total_errors) {
					/* this unqualified the current function if it has structured
					 * args or return value, or if it has nested declarations
					 */
					if (currentfunc->value.classdata.cppflags & PF_INLINE) {
						if (isstructured(currentfunc->tp->btp))
							currentfunc->value.classdata.cppflags &= ~PF_INLINE;
						else {
							SYM *head = currentfunc->tp->lst.head;
							if (head != (SYM *)-1)
								while (head) {
									if (isstructured(head->tp)) {
										currentfunc->value.classdata.cppflags &= ~PF_INLINE;
										break;
									}
									head = head->next;
								}
							if (currentfunc->value.classdata.cppflags & PF_INLINE) {
								if (arg_nesting)
									currentfunc->value.classdata.cppflags &= ~PF_INLINE;
							}
						}
					}
					if (currentfunc->value.classdata.cppflags & PF_INLINE) {
						
						currentfunc->value.classdata.inlinefunc = xalloc(sizeof(INLINEFUNC));
						currentfunc->value.classdata.inlinefunc->stmt = snp3;
						currentfunc->value.classdata.inlinefunc->syms = lsyms;
                  funcstmt = funcsym ;
					}
					else
               {
                  funcstmt = funcsym ;
                  genfunc(snp3);
               }
				} 
#ifdef CPLUSPLUS
					else
						currentfunc->value.classdata.cppflags &= ~PF_INLINE;
#endif
				if (prm_debug)
               debug_endblock(lsyms) ;
            browse_blockend(lineno) ;
				block_nesting = 0;
				block_rundown = obrd;
}
void gather_labels(TABLE *oldlsym, TABLE *lsyms)
{
	TABLE sym;
	sym.head = 0;
	if (oldlsym->head == lsyms->head) {
		return;
	}
	else {
		SYM *sp = lsyms->head,*r = oldlsym->head;
		lsyms->head = 0;
		while (sp && sp != r) {
			SYM *q = sp->next;
			sp->next = 0;
			if (sp->storage_class == sc_label || sp->storage_class == sc_ulabel) {
				if (!oldlsym->head) {
					oldlsym->head = oldlsym->tail = sp;
				}
				else {
					oldlsym->tail->next = sp;
					oldlsym->tail = sp;
				}
			}
			else
				if (!lsyms->head)
					lsyms->head = lsyms->tail = sp;
				else
					lsyms->tail = lsyms->tail->next = sp;
			sp = q;
		}
      if (oldlsym->head)
			oldlsym->tail->next = 0;
	}
}
SNODE *compoundblock(void)
{
	SNODE *snp;
	TABLE oldoldlsym;
	SYM *q;
	ENODE *obrd = block_rundown;
	block_rundown = 0;
	block_nesting++;
	oldoldlsym = oldlsym;
	oldlsym = lsyms;
	if (prm_debug)
		debug_beginblock(lsyms) ;
   browse_blockstart(lineno) ;
	cbautoinithead = 0 ;
	blockdecl();
	cseg();
	snp = compound();
	addrundown(snp);
	check_funcused(&oldlsym,&lsyms);
	q = lsyms.head;
	gather_labels(&oldlsym,&lsyms);
	while (q && q->next != oldlsym.head)
		q = q->next;
	if (q) 
		q->next = 0;
	addblocklist(lsyms.head);
	if (oldlsym.head != lsyms.head)
		arg_nesting++;	
	if (prm_debug)
      debug_endblock(lsyms) ;
   browse_blockend(lineno) ;
	lsyms = oldlsym;
	oldlsym.head = oldoldlsym.head;
	block_nesting--;
	block_rundown = obrd;
	return(snp);
}