/*
 * File: oref.c
 *  Contents: bang, random, sect, subsc
 */

#include "..\h\config.h"
#include "..\h\rt.h"
#include "rproto.h"


/*
 * !x - generate successive values from object x.
 */

OpDcl(bang,1,"!")
   {
   register word i, j, slen, rlen;
   register union block *bp;
   register dptr dp;
   register char *sp;
   int typ1;
   char sbuf[MaxCvtLen];
   FILE *fd;

#ifdef RecordIO
   word status;
#endif					/* RecordIO */

   Arg2 = Arg1;

   if (DeRef(Arg1) == Error) 
      RunErr(0, NULL);
   if ((typ1 = cvstr(&Arg1, sbuf)) != CvtFail) {
      /*
       * A string is being banged.
       */
      i = 1;
      while (i <= StrLen(Arg1)) {
         /*
          * Loop through the string using i as an index.
          */
         if (typ1 == Cvt) {
            /*
             * Arg1 was converted to a string, thus, the resulting string
             *	cannot be modified and a trapped variable is not needed.
             *	Make a one-character string out of the next character
             *	in Arg1 and suspend it.
             */
            if (strreq((word)1) == Error) 
               RunErr(0, NULL);
            StrLen(Arg0) = 1;
            StrLoc(Arg0) = alcstr(StrLoc(Arg1)+i-1, (word)1);
            Suspend;
            }
         else {
            /*
             * Arg1 is a string and thus a trapped variable must be made
             *	for the one character string being suspended.
             */
            if (blkreq((word)sizeof(struct b_tvsubs)) == Error) 
               RunErr(0, NULL);
            mksubs(&Arg2, &Arg1, i, (word)1, &Arg0);
            Suspend;
            Arg1 = Arg2;
            if (DeRef(Arg1) == Error) 
               RunErr(0, NULL);
            if (!Qual(Arg1)) 
               RunErr(103, &Arg1);
            }
         i++;
         }
      }
   else {
      /*
       * Arg1 is not a string.
       */
      switch (Type(Arg1)) {
         case T_List:
            /*
             * Arg1 is a list.  Chain through each list element block and for
             *	each one, suspend with a variable pointing to each
             *	element contained in the block.
             */
            bp = BlkLoc(Arg1);
            for (bp = bp->list.listhead; bp != NULL; bp = bp->lelem.listnext) {
               for (i = 0; i < bp->lelem.nused; i++) {
                  j = bp->lelem.first + i;
                  if (j >= bp->lelem.nslots)
                     j -= bp->lelem.nslots;
                  dp = &bp->lelem.lslots[j];
                  Arg0.dword = D_Var + ((word *)dp - (word *)bp);
                  VarLoc(Arg0) = (dptr)bp;
		  BlkLoc(Arg1) = bp;     /* save in Arg1 since bp is untended */
                  Suspend;
                  bp = BlkLoc(Arg1);	 /* bp is untended, must reset */
                  }
               }
            break;


         case T_File:
            /*
             * Arg1 is a file.  Read the next line into the string space
             *  and suspend the newly allocated string.
             */
            fd = BlkLoc(Arg1)->file.fd;

#ifdef RecordIO
            status = BlkLoc(Arg1)->file.status;
#endif					/* RecordIO */

            if ((BlkLoc(Arg1)->file.status & Fs_Read) == 0)
               RunErr(212, &Arg1);

#ifdef StandardLib
            if (BlkLoc(Arg1)->file.status & Fs_Writing) {
               fseek(fd, 0L, SEEK_CUR);
               BlkLoc(Arg1)->file.status &= ~Fs_Writing;
            }
            BlkLoc(Arg1)->file.status |= Fs_Reading;
#endif					/* StandardLib */

            for (;;) {
               StrLen(Arg0) = 0;
               do {

#ifdef RecordIO
                  if ((slen = (status & Fs_Record ?
                               getrec(sbuf, MaxCvtLen, fd) :
                               getstrg(sbuf, MaxCvtLen, fd))) == -1)
#else					/* RecordIO */
                  if ((slen = getstrg(sbuf,MaxCvtLen,fd)) == -1)
#endif                                  /* RecordIO */
                     Fail;
		  rlen = slen < 0 ? (word)MaxCvtLen : slen;
                  if (strreq(rlen) == Error) 
                     RunErr(0, NULL);
		  sp = alcstr(sbuf,rlen);
		  if (StrLen(Arg0) == 0)
                     StrLoc(Arg0) = sp;
		  StrLen(Arg0) += rlen;
		  } while (slen < 0);
                  Suspend;
               }
            break;

         case T_Table:
            /*
             * Arg1 is a table.  Generate the element values.
             */
            MakeInt(2, &Arg2);		/* indicate that we want the values */
            Forward(hgener);		/* go to the hash generator */

         case T_Set:
            /*
             * Arg1 is a set.  Generate the element values.
             */
            MakeInt(0, &Arg2);		/* indicate that we want set elements */
            Forward(hgener);		/* go to the hash generator */

         case T_Record:
            /*
             * Arg1 is a record.  Loop through the fields and suspend
             *	a variable pointing to each one.
             */
            bp = BlkLoc(Arg1);
            j = bp->record.recdesc->proc.nfields;
            for (i = 0; i < j; i++) {
               dp = &bp->record.fields[i];
               Arg0.dword = D_Var + ((word *)dp - (word *)bp);
               VarLoc(Arg0) = (dptr)bp;
               Suspend;
               bp = BlkLoc(Arg1);        /* bp is untended, must reset */
               }
            break;

         default: /* This object can not be compromised. */
            RunErr(116, &Arg1);
         }
      }

   /*
    * Eventually fail.
    */
   Fail;
   }

#define RandVal (RanScale*(k_random=(RandA*(long)k_random+RandC)&0x7fffffffL))

/*
 * ?x - produce a randomly selected element of x.
 */

OpDcl(random,1,"?")
   {
   register word val, i, j, n;
   register union block *bp, *ep;
   struct b_slots *seg;
   char sbuf[MaxCvtLen];
   dptr dp;
   double rval;

   Arg2 = Arg1;
   if (DeRef(Arg1) == Error) 
      RunErr(0, NULL);

   if (Qual(Arg1)) {
      /*
       * Arg1 is a string, produce a random character in it as the result.
       *  Note that a substring trapped variable is returned.
       */
      if ((val = StrLen(Arg1)) <= 0)
         Fail;
      if (blkreq((word)sizeof(struct b_tvsubs)) == Error) 
         RunErr(0, NULL);
      rval = RandVal;			/* This form is used to get around */
      rval *= val;			/* a bug in a certain C compiler */
      mksubs(&Arg2, &Arg1, (word)rval + 1, (word)1, &Arg0);
      Return;
      }

   switch (Type(Arg1)) {
      case T_Cset:
         /*
          * Arg1 is a cset.  Convert it to a string, select a random character
          *  of that string and return it.  Note that a substring trapped
          *  variable is not needed.
          */
         cvstr(&Arg1, sbuf);
         if ((val = StrLen(Arg1)) <= 0)
            Fail;
         if (strreq((word)1) == Error) 
            RunErr(0, NULL);
         StrLen(Arg0) = 1;
         rval = RandVal;
         rval *= val;
         StrLoc(Arg0) = alcstr(StrLoc(Arg1)+(word)rval, (word)1);
         Return;


      case T_List:
         /*
          * Arg1 is a list.  Set i to a random number in the range [1,*Arg1],
          *  failing if the list is empty.
          */
         bp = BlkLoc(Arg1);
         val = bp->list.size;
         if (val <= 0)
            Fail;
         rval = RandVal;
         rval *= val;
         i = (word)rval + 1;
         j = 1;
         /*
          * Work down chain list of list blocks and find the block that
          *  contains the selected element.
          */
         bp = bp->list.listhead;
         while (i >= j + bp->lelem.nused) {
            j += bp->lelem.nused;
            bp = bp->lelem.listnext;
            if (bp == NULL)
               syserr("list reference out of bounds in random");
            }
         /*
          * Locate the appropriate element and return a variable
          * that points to it.
          */
         i += bp->lelem.first - j;
         if (i >= bp->lelem.nslots)
            i -= bp->lelem.nslots;
         dp = &bp->lelem.lslots[i];
         Arg0.dword = D_Var + ((word *)dp - (word *)bp);
         VarLoc(Arg0) = (dptr)bp;
         Return;

      case T_Table:
      case T_Set:
          /*
           * Arg1 is a table or a set.  Set n to a random number in the range
           *  [1,*Arg1], failing if the structure is empty.
           */
         bp = BlkLoc(Arg1);
         val = bp->table.size;
         if (val <= 0)
            Fail;
         rval = RandVal;
         rval *= val;
         n = (word)rval + 1;
         /*
          * Walk down the hash chains to find and return the n'th element.
          */
         for (i = 0; i < HSegs && (seg = bp->table.hdir[i]) != NULL; i++)
            for (j = segsize[i] - 1; j >= 0; j--)
               for (ep = seg->hslots[j]; ep != NULL; ep = ep->telem.clink)
                  if (--n <= 0) {
                     if (Type(Arg1) == T_Set) {
                        /*
                         * For a set, return the element value.
                         */
                        Arg0 = ep->selem.setmem;
                        }
                     else {
                        /*
                         * For a table, return a variable pointing to the
                         *  selected element.
                         */
                        dp = &ep->telem.tval;
                        Arg0.dword = D_Var + ((word *)dp - (word *)bp);
                        VarLoc(Arg0) = (dptr)bp;
                        }
                     Return;
                     }

      case T_Record:
         /*
          * Arg1 is a record.  Set val to a random number in the range
          *  [1,*Arg1] (*Arg1 is the number of fields), failing if the
          *  record has no fields.
          */
         bp = BlkLoc(Arg1);
         val = bp->record.recdesc->proc.nfields;
         if (val <= 0)
            Fail;
         /*
          * Locate the selected element and return a variable
          * that points to it
          */
            rval = RandVal;
            rval *= val;
            dp = &bp->record.fields[(word)rval];
            Arg0.dword = D_Var + ((word *)dp - (word *)bp);
            VarLoc(Arg0) = (dptr)bp;
            Return;

#ifdef LargeInts
      case T_Bignum:
	 if (bigrand(&Arg1, &Arg0) == Error)  /* alcbignum failed */
	    RunErr(0, NULL);
	 Return;
#endif					/* LargeInts */

      default:
         /*
          * Try converting it to an integer
          */
      switch (cvint(&Arg1)) {

         case T_Integer:
            /*
             * Arg1 is an integer, be sure that it's non-negative.
             */
            val = (word)IntVal(Arg1);
            if (val < 0)
               RunErr(205, &Arg1);

            /*
             * val contains the integer value of Arg1.	If val is 0, return
             *	a real in the range [0,1], else return an integer in the
             *	range [1,val].
             */
            if (val == 0) {
               rval = RandVal;
               if (makereal(rval, &Arg0) == Error) 
                  RunErr(0, NULL);
               }
            else {
               rval = RandVal;
               rval *= val;
               MakeInt((long)rval + 1, &Arg0);
               }
            Return;

         default:
            /*
             * Arg1 is of a type for which random generation is not supported
             */
            RunErr(113, &Arg1);
            }
         }
   }

/*
 * x[i:j] - form a substring or list section of x.
 */

OpDcl(sect,3,"[:]")
   {
   register word i, j, t;
   int typ1;
   char sbuf[MaxCvtLen];

   if (blkreq((word)sizeof(struct b_tvsubs)) == Error) 
      RunErr(0, NULL);

   if (cvint(&Arg2) == CvtFail) 
      RunErr(101, &Arg2);
   if (cvint(&Arg3) == CvtFail) 
      RunErr(101, &Arg3);

   Arg4 = Arg1;
   if (DeRef(Arg1) == Error) 
      RunErr(0, NULL);

   if (Arg1.dword == D_List) {
      i = cvpos(IntVal(Arg2), BlkLoc(Arg1)->list.size);
      if (i == CvtFail)
         Fail;
      j = cvpos(IntVal(Arg3), BlkLoc(Arg1)->list.size);
      if (j == CvtFail)
         Fail;
      if (i > j) {
         t = i;
         i = j;
         j = t;
         }
      if (cplist(&Arg1, &Arg0, i, j) == Error) 
         RunErr(0, NULL);
      Return;
      }

   if ((typ1 = cvstr(&Arg1, sbuf)) == CvtFail) 
      RunErr(110, &Arg1);

   i = cvpos(IntVal(Arg2), StrLen(Arg1));
   if (i == CvtFail)
      Fail;
   j = cvpos(IntVal(Arg3), StrLen(Arg1));
   if (j == CvtFail)
      Fail;
   if (i > j) { 			/* convert section to substring */
      t = i;
      i = j;
      j = t - j;
      }
   else
      j = j - i;

   if (typ1 == Cvt) {
      /*
       * A string was created - just return a string
       */
      if (strreq(j) == Error) 
         RunErr(0, NULL);
      StrLen(Arg0) = j;
      StrLoc(Arg0) = alcstr(StrLoc(Arg1)+i-1, j);
      }
   else 				/* else make a substring tv */
      mksubs(&Arg4, &Arg1, i, j, &Arg0);
   Return;
   }

/*
 * x[y] - access yth character or element of x.
 */

OpDcl(subsc,2,"[]")
   {
   register word i, j;
   register union block *bp;
   register uword hn;
   int typ1, res;
   dptr dp;
   union block **dp1;
   char sbuf[MaxCvtLen];

   /*
    * Make a copy of Arg1.
    */
   Arg3 = Arg1;

   if (DeRef(Arg1) == Error) 
      RunErr(0, NULL);
   if ((typ1 = cvstr(&Arg1, sbuf)) != CvtFail) {
      /*
       * Arg1 is a string, make sure that Arg2 is an integer.
       */
      if (cvint(&Arg2) == CvtFail) 
         RunErr(101, &Arg2);

      /*
       * Convert Arg2 to a position in Arg1 and fail if the position is out
       *  of bounds.
       */
      i = cvpos(IntVal(Arg2), StrLen(Arg1));
      if (i == CvtFail || i > StrLen(Arg1))
         Fail;
      if (typ1 == Cvt) {
         /*
          * Arg1 was converted to a string, so it cannot be assigned back into.
          *  Just return a string containing the selected character.
          */
         if (strreq((word)1) == Error) 
            RunErr(0, NULL);
         StrLen(Arg0) = 1;
         StrLoc(Arg0) = alcstr(StrLoc(Arg1)+i-1, (word)1);
         }
      else {
         /*
          * Arg1 is a string, make a substring trapped variable for the one
          *  character substring selected and return it.
          */
         if (blkreq((word)sizeof(struct b_tvsubs)) == Error) 
            RunErr(0, NULL);
         mksubs(&Arg3, &Arg1, i, (word)1, &Arg0);
         }
      Return;
      }

   /*
    * Arg1 is not a string or convertible to one, see if it's an aggregate.
    */
   switch (Type(Arg1)) {
      case T_List:
         /*
          * Make sure that Arg2 is an integer and that the
          *  subscript is in range.
          */
         if (cvint(&Arg2) == CvtFail) 
            RunErr(101, &Arg2);
         i = cvpos(IntVal(Arg2), BlkLoc(Arg1)->list.size);
         if (i == CvtFail || i > BlkLoc(Arg1)->list.size)
            Fail;

         /*
          * Locate the list-element block containing the desired
          *  element.
          */
         bp = BlkLoc(Arg1)->list.listhead;
         j = 1;
         while (bp != NULL && i >= j + bp->lelem.nused) {
            j += bp->lelem.nused;
            bp = bp->lelem.listnext;
            }

         /*
          * Locate the desired element and return a pointer to it.
          */
         i += bp->lelem.first - j;
         if (i >= bp->lelem.nslots)
            i -= bp->lelem.nslots;
         dp = &bp->lelem.lslots[i];
         Arg0.dword = D_Var + ((word *)dp - (word *)bp);
         VarLoc(Arg0) = (dptr)bp;
         Return;

      case T_Table:
         /*
          * Arg1 is a table.  Locate the appropriate bucket
          *  based on the hash value.
          */
         if (blkreq((word)sizeof(struct b_tvtbl)) == Error) 
            RunErr(0, NULL);
         hn = hash(&Arg2);
         dp1 = memb(BlkLoc(Arg1), &Arg2, hn, &res);
         if (res == 1) {
            bp = *dp1;
            dp = &bp->telem.tval;
            Arg0.dword = D_Var + ((word *)dp - (word *)bp);
            VarLoc(Arg0) = (dptr)bp;
            }
         else {
            /*
             * Arg1[Arg2] is not in the table, make a table element trapped
             *  variable and return it as the result.
             */
            Arg0.dword = D_Tvtbl;
            BlkLoc(Arg0) = (union block *)alctvtbl(&Arg1, &Arg2, hn);
            }
         Return;

      case T_Record:
         /*
          * Arg1 is a record.  Convert Arg2 to an integer and be sure that it
          *  it is in range as a field number.
          */
         if (cvint(&Arg2) == CvtFail) 
            RunErr(101, &Arg2);
         bp = BlkLoc(Arg1);
         i = cvpos(IntVal(Arg2), (word)(bp->record.recdesc->proc.nfields));
         if (i == CvtFail || i > bp->record.recdesc->proc.nfields)
            Fail;
         /*
          * Locate the appropriate field and return a pointer to it.
          */
         dp = &bp->record.fields[i-1];
         Arg0.dword = D_Var + ((word *)dp - (word *)bp);
         VarLoc(Arg0) = (dptr)bp;
         Return;

      default:
         /*
          * Arg1 is of a type that cannot be subscripted.
          */
         RunErr(114, &Arg1);
      }
   }
