/* (C) Copyright International Business Machines Corporation 23 January */
/* 1990.  All Rights Reserved. */
/*  */
/* See the file USERAGREEMENT distributed with this software for full */
/* terms and conditions of use. */
/* Symbol Table routines for the MiniC parser
** Andy Lowry, Apr 1989
**/

static char sccsid[] = "@(#)symtab.c	1.2 3/13/90";

#include "minic.h"

/* function declarations */

static void release_entry();
static int releasable();

/* The symbol 'table' is really a linked list.  New entries are always
** placed at the front of the list, so declaration hiding by way of
** block nesting will work properly.
**/

S_ELIST *symtab = NULL;		/* no entries initially */


/* LOOKUP

** Returns the symbol table entry corresponding to the given name.  If
** CREATE is nonzero and no such entry is found, a new entry is
** created in the table with type UNKNOWN and with the current block
** nesting level.  If the given name is NULL, an anonymous entry
** structure is returned but is not placed in the table.  Returns an
** entry pointer on success, NULL on failure.
**/

S_ENTRY *
lookup(name)
char *name;
{
  S_ELIST *p;
  S_ENTRY *e;

  if (name != NULL)
    for (p = symtab; p != NULL; p = p->next) /* search all entries */
      if (strcmp(name,p->entry->name) == 0)
	return(p->entry);	/* found it */
  
  if ((e = new(S_ENTRY)) == NULL) /* allocate a new entry */
    return(NULL);
  if (name == NULL)		/* fill in the name if given */
    e->name = NULL;
  else
    e->name = copystring(name);
  e->type = S_UNKNOWN;		/* don't know what this symbol is */
  e->level = blklevel;		/* assume it belongs to current block level */
  e->refcount = 0;		/* nobody points to it yet */
  if (name != NULL) {		/* link in non-anonymous entry */
    if ((p = new(S_ELIST)) == NULL) { /* get a list cell for it */
      free(e);
      return(NULL);
    }
    p->entry = e;
    p->next = symtab;		/* place new entry at head of list */
    symtab = p;
    e->refcount++;		/* and count this reference */
  }
  return(e);
}



/* FREE_LEVEL

** Locates and removes all entries at the given block nesting level
** from the symbol table.
**/

void
free_level(level)
int level;
{
  S_ELIST *p,*p1,*prev;

  prev = NULL;			/* nothing behind us yet */
  for (p = symtab; p != NULL; p = p1) {	/* loop thru the table */
    p1 = p->next;		/* get next pointer now in case we */
				/* kill this entry */
    if (p->entry->level == level) { /* found a victim? */
      if (--(p->entry->refcount) == 0)
	free_entry(p->entry);	/* release stg if no longer referenced */
      if (prev == NULL)		/* remove entry from list */
	symtab = p1;
      else
	prev->next = p1;
      free(p);			/* and free stg for list element */
    }
    else
      prev = p;			/* keeping this entry... set it as */
				/* last kept */
  }
}


/* FREE_ENTRY

** Call this to drop a reference to a symbol table entry.  The
** reference count for the entry is dropped by one, and if as a result
** the entry can no longer be referenced, its storage is released.
**/

static void
free_entry(e)
S_ENTRY *e;
{
  e->refcount--;		/* drop the reference count */
  if (releasable(e))
    release_entry(e);		/* and release stg if possible */
}

/* RELEASABLE

** Checks to see whether a symbol table entry can be referenced other
** than "internally".  Internal references include references between
** a struct/union entry and its members, or an enum entry and its
** constituents, or a function and its arguments.  Returns nonzero iff
** the entry is releasable.
**/

static int
releasable(e)
S_ENTRY *e;
{
  S_ELIST *p;

  if (e->refcount == 0)
    return(-1);			/* unreferenced entry always releasable */
  else switch(e->type) {
  case S_FUNC:
    if (e->refcount != e->data.fn.nparm)
      return(0);		/* refed by something other than parms */
    for (p = e->data.fn.parms; p != NULL; p = p->next) /* step thru parms */
      if (!releasable(p->entry)) /* make sure each is releasable */
	return(0);
    return(-1);			/* all parms releasable, so fn is too */
    
  case S_PARM:			/* parameter to a function */
    return(e->refcount == 1);	/* ok if fn is only referent */
      
  case S_STRUCT:
  case S_UNION:			/* struct or union... */
    if (e->refcount != e->data.su.nmbr)
      return(0);		/* refed by something other than members */
    for (p = e->data.su.members; p != NULL; p = p->next) /* step thru mbrs */
      if (!releasable(p->entry)) /* make sure each is releasable */
	return(0);
    return(-1);			/* all members releasable, so are we */

  case S_MEMBER:		/* a member of a struct or union */
    return(e->refcount == 1);	/* ok if container is only referent */

  case S_ENUM:			/* an enumeration... */
    if (e->refcount != e->data.enm.necon)
      return(0);		/* refed by something besides constituents */
    for (p = e->data.enm.econs; p != NULL; p = p->next)
      if (!releasable(p->entry)) /* make sure each constituent releasable */
	return(0);
    return(-1);			/* all constituents releasalbe, so */
				/* enum is too */
    
  case S_ECON:			/* an enumeration constant */
    return(e->refcount == 1);	/* ok if enumeration is only referent */

  default:
    return(0);			/* all other symbols have no internal */
				/* referencing  */
  }
}

/* RELEASE_ENTRY

** Releases storage for a symbol table entry, and propogates the
** effects of the operation to all referents.  This routine should not
** normally be called directly, since it does not check reference
** counts to make sure there will be no dangling pointers left
** anywhere.  Instead, call free_entry, which will release storage
** only after checking that it's OK to do so.  We don't allow
** constituents to be released except via a recursive call caused by
** releasing the parent.  (An example why this is important: suppose
** we have "int f(x) int x;{...}".  If "f" is releasable, then "x"
** will also appear releasable since it must only be referred to by
** "f".  Thus a call to free_entry on "x" will go ahead and try to
** release the "x" entry.  Because of the rule stated above, that
** attempt will be ignored silently, thus preventing "f" from losing
** one of its parameter entries.)
**/

static void
release_entry(e)
S_ENTRY *e;
{
  S_ELIST *p,*p1;

  switch (e->type) {		/* do type-dependent side-effects first */

  case S_VAR:			/* variable... free up C type */
    free_ctype(e->data.var.type);
    break;

  case S_FUNC:			/* function... free type and all parms */
    free_ctype(e->data.fn.type);
    e->refcount = 0;		/* let parms know it's OK to go */
    release_elist(e->data.fn.parms); /* and release them all */
    break;

  case S_PARM:			/* function parameter */
    if (e->data.parm.fn->refcount == 0)	/* only if fn is on the way out */
      free_ctype(e->type);	/* free up its C type structure */
    else
      return;			/* do nothing otherwise */
    break;
    
  case S_TYPEDEF:		/* typedef... get rid of defining type */
    free_ctype(e->type);
    break;

  case S_STRUCT:		/* struct/union... free all members */
  case S_UNION:
    e->data.su.nmbr = 0;	/* let members know it's ok to go */
    release_elist(e->data.su.members); /* and release them all */
    break;

  case S_ENUM:			/* enumeration... free all constituents */
    e->data.enm.necon = 0;	/* let constituents know it's ok to go */
    release_elist(e->data.enm.econs); /* and release them all */
    break;

  case S_MEMBER:		/* struct/union member...  */
    if (e->data.mbr.container->data.su.nmbr == 0) /* only if parent is going */
      free_ctype(e->type);	/* free up its C type structure */
    else
      return;			/* do nothing otherwise */
    break;

  case S_ECON:			/* enumeration constant */
    if (e->data.econ.class->data.enm.necon != 0)
      return;			/* do nothing if class not being released */
    break;
  }

  /* here for common release handling... */
  if (e->name != NULL)
    free(e->name);		/* release stg for name if any */
  free(e);			/* and finally the entry itself */
}

