/*
 * File: fstruct.r
 *  Contents: delete, get, key, insert, list, member, pop, pull, push, put,
 *  set, table
 */

"delete(x1,x2) - delete element x2 from set or table x1 if it is there"
" (always succeeds and returns x1)."

function{1} delete(s,x)
   abstract {
      return type(s) ** (set ++ table)
      }

   /*
    * The technique and philosophy here are the same
    *  as used in insert - see comment there.
    */
   type_case s of {
      set:
         body {
            register uword hn;
            register union block **pd;
            int res;

            hn = hash(&x);

            pd = memb(BlkLoc(s), &x, hn, &res);
            if (res == 1) {
               /*
               * The element is there so delete it.
               */
               *pd = (*pd)->selem.clink;
               (BlkLoc(s)->set.size)--;
               }

            return s;
	    }
      table:
         body {
            register union block **pd;
            register uword hn;
            int res;

            hn = hash(&x);
            pd = memb(BlkLoc(s), &x, hn, &res);
            if (res == 1) {
               /*
                * The element is there so delete it.
                */
               *pd = (*pd)->telem.clink;
               (BlkLoc(s)->table.size)--;
               }

            return s;
            }
      default:
         runerr(122, s)
      }
end


/*
 * c_get - convenient C-level access to the get function
 *  returns 0 on failure, otherwise fills in res
 */
int c_get(struct b_list *hp, struct descrip *res)
{
   register word i;
   register struct b_lelem *bp;

   /*
    * Fail if the list is empty.
    */
   if (hp->size <= 0)
      return 0;

   /*
    * 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 result for return.
    */
   i = bp->first;
   *res = 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 1;
}

#begdef GetOrPop(get_or_pop)
#get_or_pop "(x) - " #get_or_pop " an element from the left end of list x."
/*
 * get(L) - get an element from end of list L.
 *  Identical to pop(L).
 */
function{0,1} get_or_pop(x)
   if !is:list(x) then
      runerr(108, x)

   abstract {
      return store[type(x).lst_elem]
      }

   body {
      if (!c_get((struct b_list *)BlkLoc(x), &result)) fail;
      return result;
      }
end
#enddef

GetOrPop(get) /* get(x) - get an element from the left end of list x. */
GetOrPop(pop) /* pop(x) - pop an element from the left end of list x. */


"key(T) - generate successive keys (entry values) from table T."

function{*} key(t)
   if !is:table(t) then
         runerr(124, t)

   abstract {
      return store[type(t).tbl_key]
      }

   inline {
      tended union block *ep;
      struct hgstate state;

      for (ep = hgfirst(BlkLoc(t), &state); ep != 0;
	 ep = hgnext(BlkLoc(t), &state, ep)) {
            suspend ep->telem.tref;
            }
      fail;
      }
end


"insert(x1, x2, x3) - insert element x2 into set or table x1 if not already there"
" if x1 is a table, the assigned value for element x2 is x3."
" (always succeeds and returns x1)."

function{1} insert(s, x, y)
   type_case s of {

      set: {
         abstract {
            store[type(s).set_elem] = type(x)
            return type(s)
            }

         body {
            tended union block *bp, *bp2;
            register uword hn;
            int res;
            struct b_selem *se;
            register union block **pd;

            bp = BlkLoc(s);
            hn = hash(&x);
            /*
             * If x is a member of set s then res will have the value 1,
             *  and pd will have a pointer to the pointer
             *  that points to that member.
             *  If x 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.
             */

	    /* get this now because can't tend pd */
            Protect(se = alcselem(&x, hn), runerr(0));

            pd = memb(bp, &x, hn, &res);
            if (res == 0) {
               /*
               * The element is not in the set - insert it.
               */
               addmem((struct b_set *)bp, se, pd);
               if (TooCrowded(bp))
                  hgrow(bp);
               }
	    else
	       deallocate((union block *)se);

            return s;
            }
         }

      table: {
         abstract {
            store[type(s).tbl_key] = type(x)
            store[type(s).tbl_val] = type(y)
            return type(s)
            }

         body {
            tended union block *bp, *bp2;
            union block **pd;
            struct b_telem *te;
            register uword hn;
            int res;

            bp = BlkLoc(s);
            hn = hash(&x);

	    /* get this now because can't tend pd */
            Protect(te = alctelem(), runerr(0));

            pd = memb(bp, &x, hn, &res);	/* search table for key */
            if (res == 0) {
               /*
               * The element is not in the table - insert it.
               */
               bp->table.size++;
               te->clink = *pd;
               *pd = (union block *)te;
               te->hashnum = hn;
               te->tref = x;
               te->tval = y;
               if (TooCrowded(bp))
                  hgrow(bp);
               }
            else {
	       /*
		* We found an existing entry; just change its value.
		*/
	       deallocate((union block *)te);
               te = (struct b_telem *) *pd;
               te->tval = y;
               }

            return s;
            }
         }

      default:
         runerr(122, s);
      }
end


"list(i, x) - create a list of size i, with initial value x."

function{1} list(n, x)
   if !def:C_integer(n, 0L) then
      runerr(101, n)

   abstract {
      return new list(type(x))
      }

   body {
      tended struct b_list *hp;
      register word i, size;
      word nslots;
      register struct b_lelem *bp; /* does not need to be tended */

      nslots = size = n;

      /*
       * Ensure that the size is positive and that the list-element block
       *  has at least MinListSlots slots.
       */
      if (size < 0) {
         irunerr(205, n);
         errorfail;
         }
      if (nslots == 0)
         nslots = MinListSlots;

      /*
       * 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.
       */
      Protect(hp = alclist(size), runerr(0));
      Protect(bp = alclstb(nslots, (word)0, size), runerr(0));
      hp->listhead = hp->listtail = (union block *) bp;

      /*
       * Initialize each slot.
       */
      for (i = 0; i < size; i++)
         bp->lslots[i] = x;

      /*
       * Return the new list.
       */
      return list(hp);
      }
end


"member(x1, x2) - returns x1 if x2 is a member of set or table x2 but fails"
" otherwise."

function{0,1} member(s, x)
   type_case s of {

      set: {
         abstract {
            return type(x) ** store[type(s).set_elem]
            }
         inline {
            int res;
            register uword hn;

            hn = hash(&x);
            memb(BlkLoc(s), &x, hn, &res);
            if (res==1)
               return x;
            else
               fail;
            }
         }
      table: {
         abstract {
            return type(x) ** store[type(s).tbl_key]
            }
         inline {
            int res;
            register uword hn;

            hn = hash(&x);
            memb(BlkLoc(s), &x, hn, &res);
            if (res == 1)
               return x;
            else
               fail;
            }
         }
      default:
         runerr(122, s)
      }
end


"pull(L) - pull an element from end of list L."

function{0,1} pull(x)
   /*
    * x must be a list.
    */
   if !is:list(x) then
      runerr(108, x)
   abstract {
      return store[type(x).lst_elem]
      }

   body {
      register word i;
      register struct b_list *hp;
      register struct b_lelem *bp;

      /*
       * Point at list header block and fail if the list is empty.
       */
      hp = (struct b_list *) BlkLoc(x);
      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
       *  result 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;
      result = bp->lslots[i];
      bp->nused--;
      hp->size--;
      return result;
      }
end

#ifdef Graphics
/*
 * c_push - C-level, nontending push operation
 */
void c_push(dptr l, dptr val)
{
   register word i;
   register struct b_lelem *bp; /* does not need to be tended */
   static int two = 2;		/* some compilers generate bad code for
				   division by a constant that's a power of 2*/
   /*
    * Point bp at the first list-element block.
    */
   bp = (struct b_lelem *) BlkLoc(*l)->list.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 = BlkLoc(*l)->list.size / two;
      if (i < MinListSlots)
         i = MinListSlots;
#ifdef MaxListSlots
      if (i > MaxListSlots)
         i = MaxListSlots;
#endif					/* MaxListSlots */

      /*
       * Allocate a new list element block.  If the block can't
       *  be allocated, try smaller blocks.
       */
      while ((bp = alclstb(i, (word)0, (word)0)) == NULL) {
         i /= 4;
         if (i < MinListSlots)
            fatalerr(0, NULL);
         }

      BlkLoc(*l)->list.listhead->lelem.listprev = (union block *) bp;
      bp->listnext = BlkLoc(*l)->list.listhead;
      BlkLoc(*l)->list.listhead = (union block *) bp;
      }

   /*
    * Set i to position of new first element and assign val to
    *  that element.
    */
   i = bp->first - 1;
   if (i < 0)
      i = bp->nslots - 1;
   bp->lslots[i] = *val;
   /*
    * Adjust value of location of first element, block usage count,
    *  and current list size.
    */
   bp->first = i;
   bp->nused++;
   BlkLoc(*l)->list.size++;
   }
#endif					/* Graphics */


"push(L, x1, ..., xN) - push x onto beginning of list L."

function{1} push(x, vals[n])
   /*
    * x must be a list.
    */
   if !is:list(x) then
      runerr(108, x)
   abstract {
      store[type(x).lst_elem] = type(vals)
      return type(x)
      }

   body {
      tended struct b_list *hp;
      dptr dp;
      register word i, val, num;
      register struct b_lelem *bp; /* does not need to be tended */
      static int two = 2;	/* some compilers generate bad code for
				   division by a constant that's a power of 2*/

      if (n == 0) {
	 dp = &nulldesc;
	 num = 1;
	 }
      else {
	 dp = vals;
	 num = n;
	 }

      for (val = 0; val < num; val++) {
	 /*
	  * Point hp at the list-header block and bp at the first
	  *  list-element block.
	  */
	 hp = (struct b_list *) BlkLoc(x);
	 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;
#ifdef MaxListSlots
	    if (i > MaxListSlots)
	       i = MaxListSlots;
#endif					/* MaxListSlots */

	    /*
	     * Allocate a new list element block.  If the block can't
	     *  be allocated, try smaller blocks.
	     */
	    while ((bp = alclstb(i, (word)0, (word)0)) == NULL) {
	       i /= 4;
	       if (i < MinListSlots)
		  runerr(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 to
	  *  that element.
	  */
	 i = bp->first - 1;
	 if (i < 0)
	    i = bp->nslots - 1;
	 bp->lslots[i] = dp[val];
	 /*
	  * Adjust value of location of first element, block usage count,
	  *  and current list size.
	  */
	 bp->first = i;
	 bp->nused++;
	 hp->size++;
	 }

      /*
       * Return the list.
       */
      return x;
      }
end

/*
 * c_put - C-level, nontending list put function
 */
void c_put(struct descrip *l, struct descrip *val)
{
   register word i;
   register struct b_lelem *bp;  /* does not need to be tended */
   static int two = 2;		/* some compilers generate bad code for
				   division by a constant that's a power of 2*/

   /*
    * Point hp at the list-header block and bp at the last
    *  list-element block.
    */
   bp = (struct b_lelem *) BlkLoc(*l)->list.listtail;

   /*
    * If the last list-element block is full, allocate a new
    *  list-element block, make it the last 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 = ((struct b_list *)BlkLoc(*l))->size / two;
      if (i < MinListSlots)
         i = MinListSlots;
#ifdef MaxListSlots
      if (i > MaxListSlots)
         i = MaxListSlots;
#endif					/* MaxListSlots */

      /*
       * Allocate a new list element block.  If the block can't
       *  be allocated, try smaller blocks.
       */
      while ((bp = alclstb(i, (word)0, (word)0)) == NULL) {
         i /= 4;
         if (i < MinListSlots)
            fatalerr(0, NULL);
         }

      ((struct b_list *)BlkLoc(*l))->listtail->lelem.listnext =
	(union block *) bp;
      bp->listprev = ((struct b_list *)BlkLoc(*l))->listtail;
      ((struct b_list *)BlkLoc(*l))->listtail = (union block *) bp;
      }

   /*
    * Set i to position of new last element and assign val to
    *  that element.
    */
   i = bp->first + bp->nused;
   if (i >= bp->nslots)
      i -= bp->nslots;
   bp->lslots[i] = *val;

   /*
    * Adjust block usage count and current list size.
    */
   bp->nused++;
   ((struct b_list *)BlkLoc(*l))->size++;
}


"put(L, x1, ..., xN) - put elements onto end of list L."

function{1} put(x, vals[n])
   /*
    * x must be a list.
    */
   if !is:list(x) then
      runerr(108, x)
   abstract {
      store[type(x).lst_elem] = type(vals)
      return type(x)
      }

   body {
      tended struct b_list *hp;
      dptr dp;
      register word i, val, num;
      register struct b_lelem *bp;  /* does not need to be tended */
      static int two = 2;	/* some compilers generate bad code for
				   division by a constant that's a power of 2*/
      if (n == 0) {
	 dp = &nulldesc;
	 num = 1;
	 }
      else {
	 dp = vals;
	 num = n;
	 }

      /*
       * Point hp at the list-header block and bp at the last
       *  list-element block.
       */
      for(val = 0; val < num; val++) {

	 hp = (struct b_list *)BlkLoc(x);
	 bp = (struct b_lelem *) hp->listtail;

	 /*
	  * If the last list-element block is full, allocate a new
	  *  list-element block, make it the last 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;
#ifdef MaxListSlots
	    if (i > MaxListSlots)
	       i = MaxListSlots;
#endif					/* MaxListSlots */
	    /*
	     * Allocate a new list element block.  If the block can't
	     *  be allocated, try smaller blocks.
	     */
	    while ((bp = alclstb(i, (word)0, (word)0)) == NULL) {
	       i /= 4;
	       if (i < MinListSlots)
		  runerr(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 val to
	  *  that element.
	  */
	 i = bp->first + bp->nused;
	 if (i >= bp->nslots)
	    i -= bp->nslots;
	 bp->lslots[i] = dp[val];

	 /*
	  * Adjust block usage count and current list size.
	  */
	 bp->nused++;
	 hp->size++;

	 }

      /*
       * Return the list.
       */
      return x;
      }
end


"set(L) - create a set with members in list L."
"  The members are linked into hash chains which are"
" arranged in increasing order by hash number."

function{1} set(l)

   type_case l of {
      null: {
         abstract {
            return new set(empty_type)
            }
         inline {
            register union block * ps;
            ps = hmake(T_Set, (word)0, (word)0);
            if (ps == NULL)
               runerr(0);
            return set(ps);
            }
         }

      list: {
         abstract {
            return new set(store[type(l).lst_elem])
            }

         body {
            tended union block *pb;
            register uword hn;
            dptr pd;
            struct b_selem *ne;      /* does not need to be tended */
            int res;
            word i, j;
            tended union block *ps;
            union block **pe;

            /*
             * Make a set of the appropriate size.
             */
            pb = BlkLoc(l);
            ps = hmake(T_Set, (word)0, pb->list.size);
            if (ps == NULL)
               runerr(0);

            /*
             * Chain through each list block and for
             *  each element contained in the block
             *  insert the element into the set if not there.
	     *
	     * ne always has a new element ready for use.  We must get one
	     *  in advance, and stay one ahead, because pe can't be tended.
	     */
	    Protect(ne = alcselem(&nulldesc, (uword)0), runerr(0));

            for (pb = pb->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->setmem = *pd;			/* add new element */
		     ne->hashnum = hn;
                     addmem((struct b_set *)ps, ne, pe);
							/* get another blk */
	             Protect(ne = alcselem(&nulldesc, (uword)0), runerr(0));
                     }
                  }
               }
	    deallocate((union block *)ne);
            return set(ps);
            }
         }

      default :
         runerr(108, l)
      }
end


"table(x) - create a table with default value x."

function{1} table(x)
   abstract {
      return new table(empty_type, empty_type, type(x))
      }
   inline {
      union block *bp;

      bp = hmake(T_Table, (word)0, (word)0);
      if (bp == NULL)
         runerr(0);
      bp->table.defvalue = x;
      return table(bp);
      }
end
