/*
 * File: fstruct.c
 *  Contents: delete, get, key, insert, list, member, pop, pull, push, put, set,
 *  table
 */

#include "..\h\config.h"
#include "..\h\rt.h"
#include "rproto.h"


/*
 * delete(X,x) - delete element x from set or table X if it is there
 *  (always succeeds and returns X).
 */

FncDcl(delete,2)
   {
   register union block **pd;
   register uword hn;
   int res;

   if (Qual(Arg1))
      RunErr(122, &Arg1);

   /*
   * The technique and philosophy here are the same
   *  as used in insert - see comment there.
   */
   switch (Type(Arg1)) {
      case T_Set:
      case T_Table:
         hn = hash(&Arg2);
         pd = memb(BlkLoc(Arg1), &Arg2, hn, &res);
         if (res == 1) {
            /*
            * The element is there so delete it.
            */
            *pd = (*pd)->selem.clink;
            (BlkLoc(Arg1)->set.size)--;
            }
         break;

      default:
         RunErr(122, &Arg1);
      }

   Arg0 = Arg1;
   Return;
   }


/*
 * get(x) - get an element from end of list x.
 *  Identical to pop(x).
 */

FncDcl(get,1)
   {
   register word i;
   register struct b_list *hp;
   register struct b_lelem *bp;

   /*
    * Arg1 must be a list.
    */
   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Fail if the list is empty.
    */
   hp = (struct b_list *) BlkLoc(Arg1);
   if (hp->size <= 0)
      Fail;

   /*
    * Point bp at the first list block.  If the first block has no
    *  elements in use, point bp at the next list block.
    */
   bp = (struct b_lelem *) hp->listhead;
   if (bp->nused <= 0) {
      bp = (struct b_lelem *) bp->listnext;
      hp->listhead = (union block *) bp;
      bp->listprev = NULL;
      }
   /*
    * Locate first element and assign it to Arg0 for return.
    */
   i = bp->first;
   Arg0 = bp->lslots[i];
   /*
    * Set bp->first to new first element, or 0 if the block is now
    *  empty.  Decrement the usage count for the block and the size
    *  of the list.
    */
   if (++i >= bp->nslots)
      i = 0;
   bp->first = i;
   bp->nused--;
   hp->size--;
   Return;
   }

/*
 * key(t) - generate successive keys (entry values) from table t.
 */

FncDcl(key,2)
   {
   if (Arg1.dword != D_Table) 
      RunErr(124, &Arg1);
   MakeInt(1, &Arg2);			/* indicate that we want the keys */
   Forward(hgener);			/* go to the hash generator */
   }

/*
 * insert(X,x) - insert element x into set or table X if not already there
 *  (always succeeds and returns X).
 */

FncDcl(insert,3)
   {
   register union block *bp;
   register union block **pd;
   register struct b_telem *pe;
   register uword hn;
   int res;

   if (Qual(Arg1))
      RunErr(122, &Arg1);

   switch (Type(Arg1)) {
      case T_Set:

         /*
         * We may need at most one new element.
         */
         if (blkreq((word)sizeof(struct b_selem)) == Error) 
            RunErr(0, NULL);
         bp = BlkLoc(Arg1);
         hn = hash(&Arg2);
         /*
          * If Arg2 is a member of set Arg1 then res will have the
          *  value 1 and pd will have a pointer to the pointer
          *  that points to that member.
          *  If Arg2 is not a member of the set then res will have
          *  the value 0 and pd will point to the pointer
          *  which should point to the member - thus we know where
          *  to link in the new element without having to do any
          *  repetitive looking.
          */
         pd = memb(bp, &Arg2, hn, &res);
         if (res == 0) {
            /*
            * The element is not in the set - insert it.
            */
            addmem((struct b_set *)bp, alcselem(&Arg2, hn), pd);
            if (TooCrowded(bp))
               hgrow(&Arg1);
            }
         break;

      case T_Table:
         if (blkreq((word)sizeof(struct b_telem)) == Error) 
            RunErr(0, NULL);
         bp = BlkLoc(Arg1);
         hn = hash(&Arg2);
         pd = memb(bp, &Arg2, hn, &res);
         if (res == 0) {
            /*
            * The element is not in the table - insert it.
            */
            bp->table.size++;
            pe = alctelem();
            pe->clink = *pd;
            *pd = (union block *)pe;
            pe->hashnum = hn;
            pe->tref = Arg2;
            pe->tval = Arg3;
            if (TooCrowded(bp))
               hgrow(&Arg1);
            }
         else {
            pe = (struct b_telem *) *pd;
            pe->tval = Arg3;
            }
         break;

      default:
         RunErr(122, &Arg1);
      }

   Arg0 = Arg1;
   Return;
   }

/*
 * list(n,x) - create a list of size n, with initial value x.
 */

FncDcl(list,2)
   {
   register word i, size;
   word nslots;
   register struct b_list *hp;
   register struct b_lelem *bp;

   if (defshort(&Arg1, 0) == Error) 
      RunErr(0, NULL);

   nslots = size = IntVal(Arg1);


   /*
    * Ensure that the size is positive and that the list-element block 
    *  has MinListSlots slots if its size is zero.
    */
   if (size < 0) 
      RunErr(205, &Arg1);
   if (nslots == 0)
      nslots = MinListSlots;

   /*
    * Ensure space for a list-header block, and a list-element block
    * with nslots slots.
    */
   if (blkreq(sizeof(struct b_list) + sizeof(struct b_lelem) +
         (nslots - 1) * sizeof(struct descrip)) == Error) 
      RunErr(0, NULL);

   /*
    * Allocate the list-header block and a list-element block.
    *  Note that nslots is the number of slots in the list-element
    *  block while size is the number of elements in the list.
    */
   hp = alclist(size);
   bp = alclstb(nslots, (word)0, size);
   hp->listhead = hp->listtail = (union block *) bp;

   /*
    * Initialize each slot.
    */
   for (i = 0; i < size; i++)
      bp->lslots[i] = Arg2;

   /*
    * Return the new list.
    */
   Arg0.dword = D_List;
   BlkLoc(Arg0) = (union block *) hp;
   Return;
   }

/*
 * member(X,x) - returns x if x is a member of set or table X otherwise fails.
 */

FncDcl(member,2)
   {
   int res;
   register uword hn;

   if (Qual(Arg1))
      RunErr(122, &Arg1);

   switch (Type(Arg1)) {
      case T_Set:
      case T_Table:
         hn = hash(&Arg2);
         memb(BlkLoc(Arg1), &Arg2, hn, &res);
         break;

      default:
         RunErr(122, &Arg1);
      }

   /* If Arg2 is a member of Arg1 then "res" will have the
    * value 1 otherwise it will have the value 0.
    */
   if (res == 1) {		/* It is a member. */
      Arg0 = Arg2;		/* Return the member if it is in Arg1. */
      Return;
      }
   Fail;
   }


/*
 * pop(x) - pop an element from beginning of list x.
 */

FncDcl(pop,1)
   {
   register word i;
   register struct b_list *hp;
   register struct b_lelem *bp;

   /*
    * Arg1 must be a list.
    */
   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Fail if the list is empty.
    */
   hp = (struct b_list *) BlkLoc(Arg1);
   if (hp->size <= 0)
      Fail;

   /*
    * Point bp to the first list-element block.  If the first block has
    *  no slots in use, point bp at the next list-element block.
    */
   bp = (struct b_lelem *) hp->listhead;
   if (bp->nused <= 0) {
      bp = (struct b_lelem *) bp->listnext;
      hp->listhead = (union block *) bp;
      bp->listprev = NULL;
      }
   /*
    * Locate first element and assign it to Arg0 for return.
    */
   i = bp->first;
   Arg0 = bp->lslots[i];

   /*
    * Set bp->first to new first element, or 0 if the block is now
    *  empty.  Decrement the usage count for the block and the size
    *  of the list.
    */
   if (++i >= bp->nslots)
      i = 0;
   bp->first = i;
   bp->nused--;
   hp->size--;
   Return;
   }

/*
 * pull(x) - pull an element from end of list x.
 */

FncDcl(pull,1)
   {
   register word i;
   register struct b_list *hp;
   register struct b_lelem *bp;

   /*
    * Arg1 must be a list.
    */
   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Point at list header block and fail if the list is empty.
    */
   hp = (struct b_list *) BlkLoc(Arg1);
   if (hp->size <= 0)
      Fail;
   /*
    * Point bp at the last list element block.  If the last block has no
    *  elements in use, point bp at the previous list element block.
    */
   bp = (struct b_lelem *) hp->listtail;
   if (bp->nused <= 0) {
      bp = (struct b_lelem *) bp->listprev;
      hp->listtail = (union block *) bp;
      bp->listnext = NULL;
      }
   /*
    * Set i to position of last element and assign the element to
    *  Arg0 for return.  Decrement the usage count for the block
    *  and the size of the list.
    */
   i = bp->first + bp->nused - 1;
   if (i >= bp->nslots)
      i -= bp->nslots;
   Arg0 = bp->lslots[i];
   bp->nused--;
   hp->size--;
   Return;
   }


/*
 * push(x,val) - push val onto beginning of list x.
 */
FncDcl(push,2)
   {
   register word i;
   register struct b_list *hp;
   register struct b_lelem *bp;
   static two = 2;		/* some compilers generat bad code for
				   division by a constant that's a power of 2 */


   /*
    * Arg1 must be a list.
    */
   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Point hp at the list-header block and bp at the first
    *  list-element block.
    */
   hp = (struct b_list *) BlkLoc(Arg1);
   bp = (struct b_lelem *) hp->listhead;

   /*
    * If the first list-element block is full, allocate a new
    *  list-element block, make it the first list-element block,
    *  and make it the previous block of the former first list-element
    *  block.
    */
   if (bp->nused >= bp->nslots) {
      /*
       * Set i to the size of block to allocate.
       */
      i = hp->size / two;
      if (i < MinListSlots)
         i = MinListSlots;

      /*
       * Ensure space for a new list element block.  If the block can't
       *  be allocated, try smaller blocks.
       */
      while (blkreq((word)sizeof(struct b_lelem) +
		    i * sizeof(struct descrip)) == Error) {
	    i /= 4;
	    if (i < MinListSlots)
	       RunErr(0, NULL);
	    }
      /*
       * Reset hp in case there was a garbage collection.
       */
      hp = (struct b_list *) BlkLoc(Arg1);

      bp = alclstb(i, (word)0, (word)0);
      hp->listhead->lelem.listprev = (union block *) bp;
      bp->listnext = hp->listhead;
      hp->listhead = (union block *) bp;
      }

   /*
    * Set i to position of new first element and assign val (Arg2) to
    *  that element.
    */
   i = bp->first - 1;
   if (i < 0)
      i = bp->nslots - 1;
   bp->lslots[i] = Arg2;
   /*
    * Adjust value of location of first element, block usage count,
    *  and current list size.
    */
   bp->first = i;
   bp->nused++;
   hp->size++;
   /*
    * Return the list.
    */
   Arg0 = Arg1;
   Return;
   }


/*
 * put(x,val) - put val onto end of list x.
 */

FncDcl(put,2)
   {
   register word i;
   register struct b_list *hp;
   register struct b_lelem *bp;
   static two = 2;		/* some compilers generate bad code for
				   division by a constant that's a power of 2 */

   /*
    * Arg1 must be a list.
    */
   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Point hp at the list-header block and bp at the last
    *  list-element block.
    */
   hp = (struct b_list *) BlkLoc(Arg1);
   bp = (struct b_lelem *) hp->listtail;

   /*
    * If the last list-element block is full, allocate a new
    *  list-element block, make it the first list-element block,
    *  and make it the next block of the former last list-element
    *  block.
    */
   if (bp->nused >= bp->nslots) {
      /*
       * Set i to the size of block to allocate.
       */
      i = hp->size / two;
      if (i < MinListSlots)
         i = MinListSlots;

      /*
       * Ensure space for a new list element block.  If the block can't
       *  be allocated, try smaller blocks.
       */
      while (blkreq((word)sizeof(struct b_lelem) +
		    i * sizeof(struct descrip)) == Error) {
	    i /= 4;
	    if (i < MinListSlots)
	       RunErr(0, NULL);
	    }
      /*
       * Reset hp in case there was a garbage collection.
       */
      hp = (struct b_list *) BlkLoc(Arg1);

      bp = alclstb(i, (word)0, (word)0);
      hp->listtail->lelem.listnext = (union block *) bp;
      bp->listprev = hp->listtail;
      hp->listtail = (union block *) bp;
      }

   /*
    * Set i to position of new last element and assign Arg2 to
    *  that element.
    */
   i = bp->first + bp->nused;
   if (i >= bp->nslots)
      i -= bp->nslots;
   bp->lslots[i] = Arg2;

   /*
    * Adjust block usage count and current list size.
    */
   bp->nused++;
   hp->size++;

   /*
    * Return the list.
    */
   Arg0 = Arg1;
   Return;
   }

/*
 * set(list) - create a set with members in list.
 *  The members are linked into hash chains which are
 *  arranged in increasing order by hash number.
 */
FncDcl(set,1)
   {
   register uword hn;
   register dptr pd;
   register union block *ps, *pb;
   struct b_selem *ne;
   union block **pe;
   int res;
   word i, j;

   if (ChkNull(Arg1)) {		/* Create empty set */
      ps = hmake(T_Set, (word)0, (word)0);
      if (ps == NULL)
         RunErr(0,NULL);
      Arg0.dword = D_Set;
      BlkLoc(Arg0) = ps;
      Return;
      }

   if (Arg1.dword != D_List) 
      RunErr(108, &Arg1);

   /*
    * Make a set of the appropriate size.
    */
   ps = hmake(T_Set, (word)0, BlkLoc(Arg1)->list.size);
   if (ps == NULL)
      RunErr(0, NULL);

   /*
    * Chain through each list block and for
    *  each element contained in the block
    *  insert the element into the set if not there.
    */
   for (pb = BlkLoc(Arg1)->list.listhead; pb != NULL; pb = pb->lelem.listnext) {
      for (i = 0; i < pb->lelem.nused; i++) {
         j = pb->lelem.first + i;
         if (j >= pb->lelem.nslots)
            j -= pb->lelem.nslots;
         pd = &pb->lelem.lslots[j];
         pe = memb(ps, pd, hn = hash(pd), &res);
         if (res == 0) {
            ne = alcselem(pd,hn);
            addmem((struct b_set *)ps, ne, pe);
            }
         }
      }
   Arg0.dword = D_Set;
   BlkLoc(Arg0) = ps;
   Return;
   }

/*
 * table(x) - create a table with default value x.
 */
FncDcl(table,1)
   {
   union block *bp;

   bp = hmake(T_Table, (word)0, (word)0);
   if (bp == NULL)
      RunErr(0, NULL);
   bp->table.defvalue = Arg1;
   Arg0.dword = D_Table;
   BlkLoc(Arg0) = bp;
   Return;
   }
