      subroutine exsort (dataop,maxx,list,option,outfil)
c>>   1990-02-07 exsort  w. v. snyder at jpl, 91109, convert to sftran
      external dataop
      integer maxx, list(*), option, outfil
c
c dataop is a user-coded subroutine used to perform all operations on
c     the data.  the operations include acquiring data from outside of
c     the exsort interface, manipulating scratch files and performing
c     input and output on scratch files, moving data from one memory
c     area to another, returning sorted data from the exsort interface
c     to the user program, and comparing one datum with another.
c
c     calling sequence for dataop:
c     call dataop (iop,i1,i2,iflag)
c     where all arguments are integers,
c     iop defines the operation to be performed,
c     i1 is usually an index (1-4) of a file upon which to operate,
c     i2 is usually an index (1-maxx) of the data area to use.
c     iflag is a flag to be set by dataop.
c     the values of iop and the corresponding required actions are
c     detailed below.
c
c iop  action
c
c 1   place a datum from the set to be sorted in the record area indexed
c     by i2.  i1 is irrelevant.  set the value of iflag to zero if a
c     datum is available.  set the value of iflag to any non-zero value
c     if the entire data set has been provided by this avenue.
c
c 2   write the datum in the record area indexed by i2 on the intermedi-
c     ate scratch file indexed by i1.  the value of iflag is irrelevant.
c
c 3   place an end-of-string mark (eof, unique datum, etc), to be
c     recognized during performance of operation 4 (below), on the
c     intermediate scratch file indexed by i1.  the values of i2 and
c     iflag are irrelevant.
c
c 4   read a datum from the intermediate scratch file indexed by i1 into
c     the record area indexed by i2.  set the value of iflag to zero if
c     a datum is available.  set the value of iflag to any non-zero
c     value if an end-of-string mark created by operation 3 is detected.
c
c 5   rewind the intermediate scratch file indexed by i1.  the values of
c     i2 and iflag are irrelevant.
c
c 6   if i1 is zero, a datum from the sorted data set is in the record
c     area indexed by i2.  if i1 is non-zero, the entire sorted data set
c     has been provided by this avenue.  the value of iflag is irrele-
c     vant.
c
c 7   move the datum in the record area indexed by i1 to the record area
c     indexed by i2.  the value of iflag is irrelevant.
c
c 8   compare the datum in the record area indexed by i1 to the datum in
c     the record area indexed by i2.  set iflag to some negative value
c     if the datum in the record area indexed by i1 is to be sorted
c     before the datum in the record area indexed by i2;  set the value
c     of iflag to zero if the order of the records is immaterial; set
c     iflag to some positive number if the datum in the record area
c     indexed by i1 is to be sorted after the datum in the record area
c     indexed by i2.
c
c maxx is the number of record areas available.  the in-core sort will
c     use maxx, maxx-1 or maxx-2 record areas, so maxx must be at least
c     4.
c
c list is the array used by insort for pointers.  list must be at least
c     maxx words long.
c
c option specifies the action to take if the data are initially ordered
c     or at worst disordered in blocks of less than maxx, but cannot be
c     entirely sorted in core.  if option is zero and the data are init
c     ially ordered, the value of outfil will be the index of the file
c     containing the ordered data.  if option is zero but the data are
c     not initially ordered, the value of outfil will be zero, and the
c     data will have been passed via dataop (6,i1,i2,iflag).  if option
c     is non-zero, the data will always be passed via dataop (6,...),
c     and the value of outfil will be irrelevant.
c
c     *****     external references     ********************************
c
c insrtx  sorts a block of data in memory.  insrtx is a special entry
c         in insort that allows using dataop instead of insort's usual
c         2-argument compare routine.
c pvec    converts the list produced by insrtx to a permutation vector.
c         this is done to allow a binary search in the sorted data set.
c
c
c     *****     local variables     ************************************
c
      integer npr2
      integer dum, inend, head, i, iflag, in(4), in1, in2, in3, in4, j
      integer kode, l, m, maxdat, maxind, maxstr, minind, minstr, mx1
      integer n, nbs, nstrng(4), out(2), outape, out1, out2, split, top
c
c     *****     executable statements     ******************************
c
c     initialize.
c
      outfil=0
      n=0
      kode=2
      maxdat=maxx-1
      mx1=maxx+1
      nbs=0
      minstr=1
      maxstr=2
      minind=maxx
      maxind=maxx-1
c
c     fill the user's data area and sort it.  if end-of-input does not
c     occur, write the data on a scratch file and do a merge later,
c     if necessary.
c
202     if (.not.(n.lt.maxdat)) go to 205
          n=n+1
          call dataop (1,0,n,inend)
          if (inend .eq. 0) go to 202
        n=n-1
        if (.not.(n.eq.0)) go to 207
          if (.not.(nbs.ne.0)) go to 209
            if (nbs.ne.1) go to 203
c                 one block contained all the data.  emit the data from
c                 memory, instead of reading it from scratch.
            call dataop (5,1,0,dum)
            kode=6
            outape=0
            assign 210 to npr2
c           procedure (output the block)
            go to 302
c
210         continue
209         call dataop (6,1,0,dum)
            return
207       continue
205     call insrtx (dataop,n,list,head)
        nbs=nbs+1
        outape=minstr
        if (.not.(nbs.eq.1)) go to 212
          if (.not.(inend.ne.0)) go to 214
c
          kode=6
          outape=0
          assign 215 to npr2
c         procedure (output the block)
          go to 302
c
215       call dataop (6,1,0,dum)
          return
214       do 216 i=1,4
            call dataop (5,i,0,dum)
            nstrng(i)=0
216         continue
          nstrng(1)=1
          assign 219 to npr2
c         procedure (output the block)
          go to 302
219       go to 211
c
c           another block has been sorted.  see if it will fit on an
c           existing string.
c
212     iflag=-1
        if (nstrng(2).ne.0) call dataop (8,head,maxind,iflag)
        if(iflag.lt.0)then
          call dataop (8,head,minind,iflag)
        else
          outape=maxstr
        endif
        if (.not.(iflag .ge. 0)) go to 223
c              the whole block fits after the last thing written.
          assign 224 to npr2
c         procedure (output the block)
          go to 302
224       go to 222
c
c              the sorted string won't fit on an existing string.  will
c              part of it fit?
c
223     call pvec (list,head)
        call dataop (8,list(n),minind,iflag)
        if(iflag .lt. 0)then
c
c                 none of the list will fit.  handle the list similarly
c                 to the part that won't fit.
c
          top=n
        else
c
c                 some of it will fit.  find out how much.
c
          i=1
          j=n
227       if(j-i.gt.1)then
             split=(j+i)/2
             call dataop (8,list(split),minind,iflag)
             if(iflag .ge. 0)then
               j=split
             else
               i=split
             endif
             go to 227
           endif
           split=j
c
c            write the part that will fit on intermediate scratch.
c
           do 231 j=split,n
             call dataop (2,minstr,list(j),dum)
231          continue
           call dataop (7,list(n),minind,dum)
           top=split-1
        endif
        if(nstrng(2).ne.0)then
c
c                 determine which intermediate scratch file to use for
c                 the part that won't fit.  the rule is to use the file
c                 with the least strings.  if the number of strings is
c                 the same, use the file with the maximum final datum.
c
          if(nstrng(1).ne.nstrng(2))then
            if (nstrng(outape).ge.nstrng(3-outape)) outape=3-outape
          else
            outape=maxstr
          endif
          call dataop (3,outape,0,dum)
        else
c
c                 if we are writing the first string on file 2, we must
c                 decrease the available space for sorting.
c
          outape=2
          maxdat=maxdat-1
        endif
        nstrng(outape)=nstrng(outape)+1
c
c              write the part that won't fit on intermediate scratch.
c
        do 238 j=1,top
          call dataop (2,outape,list(j),dum)
238       continue
        top=list(top)
c
c              the sorted block has been written on intermediate
c              scratch.c
222     continue
c        test end to see if we need to sort more.
211     if (inend .ne. 0) go to 203
        call dataop (7,top,mx1-outape,dum)
        n=0
        if (.not.(nstrng(2).ne.0)) go to 241
c         determine minstr etc.
          call dataop (8,minind,maxind,iflag)
          if(iflag .ge. 0)then
            i=maxstr
            maxstr=minstr
            minstr=i
            i=maxind
            maxind=minind
            minind=i
          endif
241     go to 202
c
c     all of the data have been block-sorted.  determine whether we
c     need to do a merge.
c
203   call dataop (3,1,0,dum)
      call dataop (5,1,0,dum)
      if(nstrng(2).eq.0)then
c
c        all of the data are on scratch 1.
c        see what the user wants to do.
c
        if(option.eq.0)then
          outfil=1
          return
        endif
246     call dataop (4,1,1,iflag)
        if (iflag .ne. 0) go to 247
          call dataop (6,0,1,dum)
          go to 246
247     call dataop (5,1,0,dum)
      else
c
c        we must do a merge.  set some values, and then check what
c        kind of output we do for this pass.
c
        call dataop (3,2,0,dum)
        call dataop (5,2,0,dum)
        in(1)=1
c        in(1) is to be the file with the most strings.
        if (nstrng(1).lt.nstrng(2)) in(1)=2
        in(2)=3-in(1)
        out(1)=3
        out(2)=4
        m=2
248     if(nstrng(in(1)).ne.1)then
          i=in(1)
          in(1)=in(2)
          in(2)=i
        else
          kode=6
          out(1)=0
        endif
        outape=1
c
c           read one record from each file to start the merge.  sort
c           these records.  then do the merge by writing the lowest
c           record, reading a new record from the lowest file and
c           re-ordering the records with a partial in-core merge.
c
252     call dataop (4,in(1),1,iflag)
        call dataop (4,in(2),2,iflag)
        if(m .ne. 2)then
          call dataop (4,in(3),3,iflag)
          if (m .eq. 4) call dataop (4,in(4),4,iflag)
        endif
c              sort set of first records from each file
        call insrtx (dataop,m,list,head)
c
c              write current lowest record,and then read a new record
c              from the same file.
c
256       call dataop (kode,out(outape),head,dum)
          call dataop (4,in(head),head,inend)
          i=list(head)
          if(inend .eq. 0)then
            if(i.ne.0)then
c                    if i=0, head is only remaining file
              call dataop (8,head,i,iflag)
              if(iflag .gt. 0)then
c
c                          head is no longer lowest.  merge it with
c                          chain.
c
                l=head
                head=i
264               j=list(i)
                  if (j.eq.0) go to 265
                  call dataop (8,l,j,iflag)
                  if (iflag .le. 0) go to 265
                  i=j
                  go to 264
265             list(i)=l
                list(l)=j
              endif
            endif
          else
c
c                    a string has terminated.
c
            l=in(head)
            nstrng(l)=nstrng(l)-1
            if (nstrng(l).eq.0) call dataop (5,l,0,dum)
            if (i.eq.0) go to 257
            head=i
          endif
          go to 256
c
c              all strings have terminated.  if we are doing final
c              output we are done.
c
257   if (kode.eq.6) go to 249
c
c              determine whether to continue the current merge pass or
c              start a new one.
c
        l=out(outape)
        nstrng(l)=nstrng(l)+1
        call dataop (3,l,0,dum)
        j=nstrng(in(1))+nstrng(in(2))
        if(j.eq.2)then
          if(nstrng(out(1)).eq.1)then
c
c                    the total remaining input string count is 2.  the
c                    total output string count is 1 or 2.  we will do
c                    final output with a merge order of 3 or 4
c                    depending on whether the total output string count
c                    is 1 or 2.
c
            in(3)=out(1)
            m=4
            call dataop (5,out(1),0,dum)
            if(nstrng(out(2)).ne.0)then
              m=5
              in(4)=out(2)
              call dataop (5,out(2),0,dum)
            endif
            kode=6
            out(1)=0
            outape=2
          endif
        else if(nstrng(in(1)).eq.0) then
          go to 253
        endif
        outape=3-outape
        m=max(m-1,2)
        go to 252
c
c           we must start a new merge pass.  swap input and output
c           files.  if the total remaining input string count is 1,
c           the merge order can be temporarily raised to 3.
c
253     m=2
c           nstrng(in(2)) is always .ge. nstrng(in(1)).
        call dataop (5,out(1),0,dum)
        if(nstrng(out(2)).ne.0)then
          if(nstrng(in(2)).ne.0)then
            m=3
            in(3)=in(2)
          endif
          call dataop (5,out(2),0,dum)
          i=in(2)
          in(2)=out(2)
          out(2)=i
        endif
        i=in(1)
        in(1)=out(1)
        out(1)=i
        go to 248
249     continue
      endif
c
      call dataop (6,1,0,dum)
      return
c
c
c
c
c     procedure (output the block)
302   m=head
279   if(m.ne.0)then
         call dataop (kode,outape,m,iflag)
         top=m
         m=list(top)
         go to 279
      endif
      go to npr2,(210,215,219,224)
c
      end
