FSEWORK 
PROC FSEWORK; 
 BEGIN
  
  
# 
***       FSEWORK - WORKFILE ACCESS METHOD FOR FULL SCREEN EDITOR.
* 
*         COPYRIGHT CONTROL DATA SYSTEMS INC.  1992.
# 
  
  DEF LISTCON #0#;
  
CONTROL EJECT;                         # UNIVERSAL DEFINITIONS       #
  
*IFCALL SINGLE,COMFSGL
*IFCALL ONLY,COMFONL
*IFCALL MULTI,COMFMLT 
*CALL COMFFSE 
  
  
  CONTROL IFEQ MULTI,1; 
    DEF RECALL(AA) #SMFRCL(AA)#;
    DEF DELAY      #SMFDLY#;
    XREF PROC SMFRCL; 
    XREF PROC SMFDLY; 
  CONTROL FI; 
  CONTROL IFEQ SINGLE,1;
    XREF PROC RECALL; 
    DEF DELAY      #RECALL(0)#; 
  CONTROL FI; 
  
  XREF                       # SYSTEM PROCS      #
    BEGIN 
    PROC WRITE; 
    PROC REWRITE; 
    PROC RPHR;
    PROC RPHRLS;
    PROC SKIPEI;
    PROC REWIND;
    PROC READ;
    PROC WRITER;
    PROC WRITEW;
    PROC REWRITR; 
    CONTROL IFEQ SINGLE,1;
      PROC EVICT; 
    CONTROL FI; 
    END 
  
  XREF                       # COMPASS PROCS     #
    BEGIN 
    PROC MOVEWD;
    PROC EXCHWD;
    FUNC MOVELN;
    FUNC LINESZ;
    END 
  
  XREF
    BEGIN 
*CALL COMFXSB 
    END 
  
  XDEF
    BEGIN 
*CALL COMFXWK 
    END 
  
                             # COMMON DATA       #
  
CONTROL IFEQ MULTI,1; 
  XREF ARRAY RENTSTK [1:MAXREENT];     # SUBROUTINE STACK  #
    BEGIN 
    ITEM RSTK;
    END 
  XREF ITEM RSTKPTR;
  XREF PROC WIOSTAT;
CONTROL FI; 
  
*CALL COMFDS1 
*CALL COMFVD2 
*CALL COMFDS2 
PAGE
  
  
PROC INITFET(BUFF,LEN);      # INITIALIZE FET    #
  BEGIN 
# 
**        INITFET - INITIALIZE FILE ENVIRONMENT TABLE.
* 
*         INITFET IS SPECIALIZED FOR THE WORKFILE MANAGER.  THE FET 
*         IS ASSUMED TO BE MAPPED BY THE BASED ARRAY "FET". THE 
*         LENGTH OF THE FET IS ASSUMED TO BE AT LEAST SEVEN WORDS,
*         AND RANDOM I/O FLAGS WILL BE SET. 
* 
*         ENTRY  BUFF - THE ARRAY PROVIDED AS A BUFFER. 
*                LEN - INTEGER LENGTH OF BUFFER.
* 
*         EXIT   FET - INITIALIZED FOR RANDOM I/O.
*                QUARTER - INITIALIZED AS APPROX LEN/4. 
# 
  ARRAY BUFF;;
  ITEM LEN; 
  QUARTER=(LEN+O"300")/4; 
  FETNAM = WORKNAM; 
  FETCOD = 0; 
  FETDUN = TRUE;
  FETR = TRUE;
  FETW = FALSE; 
  FETOUT = LOC(BUFF); FETLIM = LOC(BUFF) + LEN; 
  FETL = 2; 
  FETFIR = LOC(BUFF); FETIN = LOC(BUFF);
  END 
  
  
FUNC BUFAVAIL I;
  BEGIN 
# 
**        BUFAVAIL - FUNCTION TO COMPUTE BUFFER FREE SPACE. 
* 
*         BUFAVAIL COMPUTES THE SPACE AVAILABLE IN THE CIRCULAR 
*         BUFFER ASSIGNED TO THE FET MAPPED BY THE BASED ARRAY
*         "FET".
* 
*         ENTRY  FET - FET. 
* 
*         EXIT   BUFAVAIL - WORDS OF SPACE STILL AVAILABLE FOR DATA.
# 
  IF FETIN LS FETOUT THEN BUFAVAIL=FETOUT-FETIN;
  ELSE BUFAVAIL=(FETLIM-FETFIR)-(FETIN-FETOUT); 
  END 
  
  
FUNC BUFUSAGE I;
  BEGIN 
# 
**        BUFUSAGE - FUNCTION TO COMPUTE BUFFER UTILIZATION.
* 
*         BUFUSAGE IS THE COUTERPART TO BUFAVAIL, COMPUTING THE 
*         NUMBER OF WORDS ALREADY USED IN THE CIRCULAR BUFFER 
*         ASSIGNED TO THE FET MAPPED BY BASED ARRAY "FET".
* 
*         ENTRY  FET - FET. 
* 
*         EXIT   BUFUSAGE - RESULT. 
# 
  IF FETIN GQ FETOUT THEN BUFUSAGE=FETIN-FETOUT;
  ELSE BUFUSAGE=(FETLIM-FETFIR)-(FETOUT-FETIN); 
  END 
  
  
FUNC LSTUSAGE I;
  BEGIN 
# 
**        LSTUSAGE - COMPUTE UTILIZATION OF READ LIST.
* 
*         LSTUSAGE COMPUTES THE NUMBER OF WORDS USED IN THE READ
*         LIST. 
* 
*         ENTRY  RDIN, RDOUT - BOUNDS FOR PORTION OF LIST USED. 
*                LSTSIZE - SIZE OF LIST.
* 
*         EXIT   LSTUSAGE - RESULT. 
# 
  LSTUSAGE=RDIN-RDOUT;
  IF RDIN LS RDOUT THEN LSTUSAGE=LSTSIZE-1-RDOUT+RDIN;
  END 
  
  
FUNC LSTAVAIL I;
  BEGIN 
# 
**        LSTAVAIL - COMPUTE SPACE AVAILABLE IN READ LIST.
* 
*         LSTAVAIL COMPUTES THE NUMBER OF WORDS AVAILABLE FOR USE 
*         IN THE RANDOM READ LIST.
* 
*         ENTRY - RDIN, RDOUT, LSTSIZE - CONTROL READ LIST. 
* 
*         EXIT   LSTAVAIL - RESULT. 
* 
*         CALLS  LSTUSAGE.
# 
  LSTAVAIL=LSTSIZE-1-LSTUSAGE;
  END 
  
  
PROC RDWBUF(LENGTH);
  BEGIN 
# 
**        RDWBUF - READ WORDS FROM BUFFER WITH NO I/O.
* 
*         RDWBUF FUNCTIONS AS A SPECIALIZED READW MACRO.  A STANDARD
*         FET IS ASSUMED AND THE DATA TRANSFER WORKING BUFFER IS
*         ASSUMED TO BE MAPPED BY A STANDARD BASED ARRAY.  MOST 
*         IMPORTANT, THIS ROUTINE IS GUARANTEED TO NEVER CALL CIO.
*         THE CALLER IS RESPONSIBLE TO ASSURE THAT THE CIRCULAR 
*         BUFFER HAS ENOUGH DATA TO SATISFY THE REQUEST.
* 
*         ENTRY  LENGTH - AMMOUNT OF DATA NEEDED. 
*                FET - FET AND CIRCULAR BUFFER HAVE ENOUGH DATA.
*                TOO - MAPPED TO USER'S WORKING BUFFER. 
* 
*         EXIT   FET - INDICATES DATA TRANSFERRED.
*                TOO - DATA IS TRANSFERRED. 
* 
*         CALLS  MOVEWD, TRAGIC, BUFUSAGE.
* 
*         USES   FROM.
# 
  ITEM LENGTH, X1;
  IF BUFUSAGE LS LENGTH OR LENGTH LS 0 THEN 
    BEGIN 
    TRAGIC(" WORKFILE BUFFER EMPTY ON READ.$"); 
    END 
  P<FROM>=FETOUT; 
  IF FETIN GR FETOUT OR FETLIM-FETOUT GR LENGTH THEN
    BEGIN 
    MOVEWD(LENGTH,FROM,TOO);
    FETOUT=FETOUT+LENGTH; 
    END 
  ELSE
    BEGIN 
    X1=FETLIM-FETOUT; 
    MOVEWD(X1,FROM,TOO);
    P<TOO>=LOC(TOO)+X1; 
    X1=LENGTH-X1; 
    P<FROM>=FETFIR; 
    MOVEWD(X1,FROM,TOO);
    FETOUT=FETFIR+X1; 
    END 
  END 
  
  
PROC WTWBUF(LENGTH);
  BEGIN 
# 
**        WTWBUF - WRITE WORDS TO BUFFER WTH NO I/O.
* 
*         WTWBUF PERFORMS THE ROLE OF THE WRITEW MACRO, BUT IS
*         SPECIALIZED TO USE AN ASSUMED FET AND WORKING BUFFER, AND 
*         IS GUARANTEED TO NEVER PERFORM ANY CIO CALLS.  THE CALLER 
*         IS RESPONSIBLE TO ASSURE THE CIRCULAR BUFFER HAS ROOM TO
*         ACCEPT THE DATA.
* 
*         ENTRY  LENGTH - NUMBER OF WORDS.
*                FET - THE CIRCULAR BUFFER HAS ROOM.
*                FROM - MAPPED ONTO WORKING BUFFER. 
* 
*         EXIT   FET - THE CIRCULAR BUFFER HAS DATA.
* 
*         CALLS  BUFAVAIL, TRAGIC, MOVEWD.
* 
*         USES   TOO. 
# 
  ITEM LENGTH, X1;
  IF BUFAVAIL LS LENGTH OR LENGTH LS 0 THEN 
    BEGIN 
    TRAGIC(" WORKFILE BUFFER FULL ON WRITE.$"); 
    END 
  P<TOO>=FETIN; 
  IF FETOUT GR FETIN OR FETLIM-FETIN GR LENGTH THEN 
    BEGIN 
    MOVEWD(LENGTH,FROM,TOO);
    FETIN=FETIN+LENGTH; 
    END 
  ELSE
    BEGIN 
    X1=FETLIM-FETIN;
    MOVEWD(X1,FROM,TOO);
    P<FROM>=LOC(FROM)+X1; 
    X1=LENGTH-X1; 
    P<TOO>=FETFIR;
    MOVEWD(X1,FROM,TOO);
    FETIN=FETFIR+X1;
    END 
  END 
  
PROC WAIT;
  IOBEGIN(WAIT) 
# 
**        WAIT - WAIT (RECALL) FOR I/O TO FINISH. 
* 
*         WAIT DELAYS THIS TASK UNTIL THE FET IS FOUND TO INDICATE
*         COMPLETION OF A PREVIOUS I/O OPERATION.  WAIT IS A NO-OP
*         IF THE FET ALREADY INDICATES COMPLETION.
* 
*         ENTRY  FETDUN - SHOWS WHETHER DELAYS REALLY NEEDED. 
* 
*         EXIT   FETDUN - TRUE. 
* 
*         CALLS  RECALL.
# 
  IF NOT FETDUN THEN
    BEGIN 
    RECALL(FET);
    END 
  IOEND 
  
  
PROC DISOWNCACHE(PARM); 
  BEGIN 
# 
**        DISOWNCACHE - RELEASE CACHE FRAME FROM OWNERSHIP. 
* 
*         DISOWNCACHE IS USED TO RELEASE A PAGE FROM OWNERSHIP BY 
*         A LEVEL OF THE TREE STRUCTURE.  RELEASE OF A PAGE MAKES 
*         IT ELIGIBLE TO BE FLUSHED OUT OF MEMORY (AND POSSIBLY 
*         REWRITTEN BACK TO DISK) ANYTIME THE FLUSHCACHE ROUTINE
*         IS NEEDED.  DISOWNCACHE ALSO SETS THE AGE FOR THE PAGE
*         SO THAT FLUSHCACHE CAN DO ANY REWRITES IN THE MANDATORY 
*         ORDER OF RELEASE.  THE GLOBAL AGE COUNTER IS INCREMENTED. 
* 
*         ENTRY  PARM - TREE LEVEL. 
*                CACHEOWNED[PARM] - TRUE FOR NON-TRIVIAL OPERATON.
*                AGECOUNT - HIGHEST AGE YET ASSIGNED TO ANY PAGE. 
* 
*         EXIT   AGECOUNT - INCREMENTED.
*                CACHEOWNED[PARM] - FALSE.
*                CACHEAGE[PARM] - SETUP.
# 
  ITEM PARM;
  IF PARM LS 0 OR NOT CACHEOWNED[PARM] THEN RETURN; 
  CACHEOWNED[PARM]=FALSE; 
  AGECOUNT=AGECOUNT+1;
  CACHEAGE[PARM]=AGECOUNT;
  END                         # OF DISOWNCACHE        # 
  
  
FUNC COUNTCACHE;
  BEGIN 
# 
**        COUNTCACHE - DETERMINE HOW MANY CACHE FRAMES ARE NOT USED.
* 
*         COUNTCACHE TELLS THE CALLER HOW MANY CACHE FRAMES ARE 
*         NEITHER OWNED BY ANY TREE LEVEL NOR CONTAIN DATA, WHICH 
*         HAS BEEN ALTERED IN THE CACHE BUT NOT YET ON DISK.
* 
*         ENTRY  CACHEOWNED[ALL] - SETUP. 
*                CACHEDIRTY[ALL] - SETUP. 
* 
*         EXIT   COUNTCACHE - RESULT. 
# 
  ITEM I, J;
  J=0;
  FOR I=0 STEP 1 UNTIL MAXCACHE DO IF NOT 
    (CACHEDIRTY[I] OR CACHEOWNED[I]) THEN J=J+1;
  COUNTCACHE=J; 
  END                         # OF COUNTCACHE     # 
  
  
PROC SEARCHCACHE(M);
  BEGIN 
# 
**        SEARCHCACHE - DETERMINE BEST CACHEFRAME TO TAKE OVER. 
* 
*         SEARCHCACHE TESTS ALL CACHE FRAMES FOR THOSE WHICH ARE
*         AVAILABLE TO BE CLAIMED AND USED FOR DIFFERENT DATA THAN
*         PRESENTLY IN MEMORY.  THE OLDEST AVAILABLE FRAME IS 
*         CONSIDERED THE BEST CANDIDATE FOR RECYCLE.  A FRAME 
*         IS AVAILABLE IF IT IS NEITHER CURRENTLY OWNED BY ANY TREE 
*         LEVEL NOR CONTAINS DATA ALTERED IN MEMORY BUT NOT ON DISK.
* 
*         ENTRY  CACHEOWNED[ALL] - SETUP. 
*                CACHEDIRTY[ALL] - SETUP. 
*                CACHEAGE[ALL] - SETUP FOR UNOWNED, UNDIRTY FRAMES. 
* 
*         EXIT   M - CHOSEN FRAME.
* 
*         CALLS  TRAGIC.
# 
  ITEM K,L,M; 
  M=-1; 
  K=999999999;
  FOR L=0 STEP 1 UNTIL MAXCACHE DO
    BEGIN 
    IF CACHEAGE[L] LS K AND 
      NOT (CACHEDIRTY[L] OR CACHEOWNED[L]) THEN 
      BEGIN               # SEARCH OLDEST AVAIL PAGE    # 
      M=L;
      K=CACHEAGE[L];
      END 
    END 
  IF M LS 0 THEN TRAGIC(" ALL WORKFILE BUFFERS ARE FULL.$");
  END                         # OF SEARCHCACHE     #
  
  
PROC ALLOCCACHE;
  BEGIN 
# 
**        ALLOCCACHE - LAY CLAIM TO A CACHE FRAME.
* 
*         ALLOCCACHE IS CALLED TO CLAIM A FRAME OF MEMORY IN THE
*         DISK CACHE, AND ATTACH IT TO A PARTICULAR TREE LEVEL. 
*         THE DISK ADDRESS OF THE DISK DATA IS SAVED IN THE HEADER
*         WORD OF THE CACHE FRAME, SO IT CAN BE MATCHED FOR 
*         PURPOSES OF AVOIDING DISK READS.
* 
*         ENTRY  PL - CURRENT TREE LEVEL. 
*                PADA[PL] - DISK ADDRESS ASSIGNED FOR REAL DATA.
* 
*         EXIT   PACACHE[PL] - WHICH CACHE FRAME WAS SELECTED.
*                BFDA[PL] - DISK ADDRESS IS STORED IN CACHE FRAME.
* 
*         CALLS  SEARCHCACHE. 
# 
  ITEM TMP1;
  SEARCHCACHE(TMP1);
  PACACHE[PL]=TMP1; 
  CACHEOWNED[TMP1]=TRUE;
  BFDA[TMP1]=PADA[PL];
  END                         # OF ALLOCCACHE     # 
  
  
PROC RECLAIMCACHE;
  BEGIN 
# 
**        RECLAIMCACHE - IDENTIFY CACHE FRAME WRONGLY RELEASED. 
* 
*         RECLAIMCACHE IS USED TO MATCH A TREE LEVEL TO A CACHE 
*         FRAME WHICH CONTAINS THE VIRTUAL DISK SECTOR FOR THE
*         TREE LEVEL, AND REESTABLISH OWNERSHIP.  THIS IS DONE
*         BECAUSE THERE ARE SITUATIONS WHERE A PAGE MUST BE 
*         TEMPORARILY RELEASED FROM OWNERSHIP SO IT CAN BE WRITTEN
*         TO DISK, BUT FORFEITURE OF OWNERSHIP IS NOT INTENDED
*         FOR ANY OTHER REASON.  RECLAIMCACHE CAN BE CALLED AFTER 
*         A SEQUENCE OF DISOWNCACHE AND FLUSHCACHE, AND THE TREE
*         LEVELS ARE THEN AS THEY WERE BUT DATA ALTERATIONS ARE 
*         UP TO DATE ON DISK.  REMEMBER THAT THE ORDER IN WHICH PAGES 
*         CAN BE UPDATED TO DISK DEPENDS ON THE TREE HIERARCHY, SO
*         IT IS SOMETIMES NECESSARY TO GO THRU THIS SEQUENCE WITH ONE 
*         TREE LEVEL IN ORDER TO ACHIEVE THE ACTUAL PURPOSE OF
*         WRITING A DIFFERENCE TREE LEVEL TO DISK.
* 
*         ENTRY  PL - CURRENT TREE LEVEL. 
*                PALVL - DEEPEST VALID TREE LEVEL.
*                CACHEOWNED[ALL], PACACHE[ALL], BFDA[ALL] - SETUP.
* 
*         EXIT   CACHEOWNED[PL THRU PALVL] - TRUE.
* 
*         CALLS  TRAGIC.
* 
*         USES   TPL. 
# 
  FOR TPL=PL STEP 1 UNTIL PALVL DO
    BEGIN 
    IF CACHEOWNED[PACACHE[TPL]] THEN TEST TPL;
    IF BFDA[PACACHE[TPL]] NQ PADA[TPL] THEN 
      BEGIN 
      TRAGIC(" WORKFILE BUFFER CONTAINS WRONG TEXT.$"); 
      END 
    CACHEOWNED[PACACHE[TPL]]=TRUE;
    END 
  END                         # OF RECLAIMCACHE   # 
PAGE
PROC MOVEIO;
  BEGIN 
# 
**        MOVEIO - INITIATE ANY CIO CALLS AS NEEDED.
* 
*         MOVEIO IS CALLED ANY TIME CIO CALLS ARE KNOWN TO BE 
*         NECESSARY, AND CAN ALSO BE CALLED ANY TIME CIO CALLS MIGHT
*         BE DESIRABLE.  MOVEIO BECOMES A NO-OP IF A CIO CALL IS
*         ALREADY IN PROGRESS, OR IF NO CIO CALL IS NEEDED.  THE
*         CALLER IS RESPONSIBLE TO PREPARE THE FET AND CIRCULAR 
*         BUFFER IN ALL WAYS EXCEPT THE CHOICE OF CIO FUNCTION CODE.
* 
*         WRITES ARE INITIATED WHEN THE IOWRITE FLAG HAS BEEN 
*         PREPARED TO INDICATE THAT WRITES ARE NEEDED, THE FET IS NOT 
*         PREVIOUSLY INTERLOCKED, AND THE CIRCULAR BUFFER CONTAINS
*         SOME DATA.  READS ARE INITIATED WHEN THE IOREAD FLAG HAS
*         BEEN PREPARED TO INDICATE THAT READS ARE NEEDED, THE FET IS 
*         NOT PREVIOUSLY INTERLOCKED, AND THE RDIN/RDOUT POINTERS 
*         INDICATE THERE IS A NON-TRIVIAL LIST OF PRUS STILL AWAITING 
*         READS.
* 
*         MOVEIO ALSO PERFORMS THE FUNCTION OF RECOGNIZING THAT ALL 
*         DESIRED I/O HAS BEEN COMPLETED, SUCH AS ALL WRITEABLE DATA
*         REMOVED FROM CIRCULAR BUFFER OR ALL READABLE PRUS REMOVED 
*         FROM READ LIST.  UNDER THESE CONDITIONS, MOVEIO CLEARS THE
*         IOXXXX FLAGS SO THAT NO FURTHER DESIRE FOR CIO CALLS WILL 
*         BE INDICATED, UNTIL AND UNLESS SOMEONE TURNS THE FLAGS ON 
*         AGAIN.
* 
*         IN THE CASES WHERE THE CIRCULAR BUFFER CONTAINS DATA TO BE
*         WRITTEN, THE ACTUAL CIO FUNCTION CODE IS CHOSEN FROM FOUR 
*         POSSIBILITIES BASED ON EXTENSION/ALTERATION OF THE FILE AND 
*         NEED OR NON-NEED FOR END-OF-RECORD TO BE WRITTEN.  WHEN THE 
*         DISK IS TO BE READ, THE FUNCTION CODE IS ALWAYS "RPHRLS", 
*         AKA "READ PRUS RANDOMLY BY LIST". 
* 
*         NOTE THAT MOVEIO NEVER WAITS FOR ANY CIO TO COMPLETE, THUS
*         MOVEIO IS NOT A REENTRANT ROUTINE.
* 
*         ENTRY  FETDUN - WHETHER ANY PREVIOUS I/O IS DONE. 
*                IOACT - WHETHER I/O SHOULD BE DONE SOMETIME SOON.
*                IOWRITE - WHETHER I/O NEEDED IS A WRITE. 
*                IOREAD - WHETHER I/O NEEDED IS A READ. 
*                IOAPEND - WHETHER WRITE IS TO EXTEND FILE OR ALTER.
*                IOWRTEOR - WHETHER WRITE IS TO PRODUCE EOR.
*                FETIN, FETOUT - INDICATE WHETHER UNWRITTEN DATA IS 
*                    STILL IN THE BUFFER. 
*                RDIN, RDOUT - INDICATE WHETHER THERE ARE PRUS
*                    IDENTIFIED IN READ LIST AS DESIRABLE TO READ,
*                    BUT NOT YET READ IN. 
*                FETLA, FETLAW - CURRENT POSITION IN READ LIST. 
*                RDLIST[ALL] - SETUP. 
*                CIOCOUNT - ACCOUNTING ACCUMULATOR. 
*                MAXDA, LASTDA - USED TO DETERMINE VALIDITY OF
*                    WRITE PARAMETERS, WHEN CONSISTENCY CHECKING
*                    IS ENABLED IN THE SOURCE CODE. 
*                FETCRI - USED WITH MAXDA AND LASTDA FOR VALIDITY.
* 
*         EXIT   FETDUN - FALSE IF A CIO CALL STARTED UP. 
*                CIOCOUNT - INCREMENTED IF CIO CALLED.
*                IOACT, IOREAD, IOWRITE, IOAPEND - CLEARED IF AND 
*                    ONLY IF ALL MOVEIO DETERMINES THAT ALL 
*                    PREVIOUSLY REQUESTED I/O HAS COMPLETED.
* 
*         CALLS  WRITE, REWRITE, WRITER, REWRITR, RPHRLS, TRAGIC. 
# 
  ITEM I; 
  SWITCH CALLWRITE WRTFUNC,RWRTFUNC,WRTRFUNC,RWRTRFUNC; 
  
  IF IOACT AND FETDUN THEN
   BEGIN
  
    IF IOWRITE AND FETIN NQ FETOUT THEN          # WRITE NOT DONE    #
      BEGIN 
      CIOCOUNT = CIOCOUNT + 1;
  
      I=1;
      IF IOAPEND THEN I=0;
      IF IOWRTEOR THEN I=I+2; 
      IOWRTEOR=FALSE; 
      GOTO CALLWRITE[I];
  
WRTFUNC:  
      WRITE(FET,0); 
      RETURN; 
  
RWRTFUNC: 
      FETEP = TRUE;                  # SET ERROR PROCESSING BIT       # 
      REWRITE(FET,0); 
      FETEP = FALSE;                 # CLEAR ERROR PROCESSING BIT     # 
      IF FETAT EQ O"22" THEN
        BEGIN                        # IF FATAL ERROR                 # 
        TRAGIC(" WORKFILE BUFFER EMPTY ON REWRITE.$");
        END 
      RETURN; 
  
WRTRFUNC: 
      WRITER(FET,0);
      RETURN; 
  
RWRTRFUNC:  
      FETEP = TRUE;                  # SET ERROR PROCESSING BIT       # 
      REWRITR(FET,0); 
      FETEP = FALSE;                 # CLEAR ERROR PROCESSING BIT     # 
      IF FETAT EQ O"22" THEN
        BEGIN                        # IF FATAL ERROR                 # 
        TRAGIC(" WORKFILE BUFFER EMPTY ON REWRITER. $");
        END 
      RETURN; 
  
      END 
  
    ELSE IF IOREAD AND RDIN NQ RDOUT THEN        # READ NOT DONE     #
      BEGIN 
      IF FETLA EQ LOC(RDLIST[RDLIM])
        THEN FETLAW = LOC(RDLIST[0]); 
  
      IF FETLA NQ LOC(RDLIST[RDIN]) THEN
        BEGIN 
        CIOCOUNT = CIOCOUNT + 1;
        RPHRLS(FET,0);
        END 
      END 
  
    ELSE                               # READ AND WRITE DONE         #
      BEGIN 
  
      CONTROL IFGQ PARANOIA,5;
        IF IOWRITE THEN                # CHECK WRITE CRI   #
          BEGIN 
          IF IOAPEND THEN 
            BEGIN 
            IF FETCRI NQ MAXDA+1 THEN 
              BEGIN 
              TRAGIC(" FILE SIZE INCORRECT.$"); 
              END 
            END 
          ELSE
            BEGIN 
            IF FETCRI NQ LASTDA+1 THEN
              BEGIN 
              TRAGIC(" RANDOM ADDRESS INCORRECT.$");
              END 
            END 
          END 
      CONTROL FI; 
  
      IOACT = FALSE;
      IOWRITE = FALSE; IOAPEND = FALSE; 
      IOREAD = FALSE; 
      END 
  
    END 
  END 
PAGE
PROC PAUSEIO; 
  IOBEGIN(PAUSEIO)
# 
**        PAUSEIO - AWAIT AND/OR ENFORCE CIO COMPLETION.
* 
*         PAUSEIO IS CALLED FOR ONE OF THREE REASONS:  (1) THE CALLER 
*         HAS STARTED SOME I/O VIA MOVEIO AND NEEDS TO WAIT FOR IT TO 
*         FINISH, OR (2) THE CALLER NEEDS SOME OLD I/O TO FINISH SO 
*         THAT THE FET CAN BE USED TO INITIATE NEW DESIRED I/O, OR
*         (3) THE CALLER NEEDS SOME OLD I/O TO FINISH, TO FREE THE
*         CIRCULAR BUFFER AND READ LIST SO THOSE MEMORY RESOURCES CAN 
*         BE MANAGED AND POSSIBLY RE-ASSIGNED.
* 
*         PAUSEIO THEREFORE GUARANTEES THAT ANY WRITEABLE DATA IN THE 
*         CIRCULAR BUFFER REALLY GETS WRITTEN, AND THAT ANY INITIATED 
*         READS ARE FINISHED WITH THE DATA DISCARDED AND THE CIRCULAR 
*         BUFFER EMPTY.  PAUSEIO IS ALLOWED TO HANDLE READS WHICH 
*         HAVE BEEN REQUESTED BUT NOT YET INITIATED BY SIMPLE 
*         CANCELLATION.  THUS, PAUSEIO IS MEANINGFUL TO THE TYPE 1
*         CALLER ONLY FOR THE WRITE DIRECTION - THE READ DIRECTION IS 
*         A THROWAWAY WHILE THE WRITE DIRECTION HAS ASSURANCE OF
*         COMPLETION. 
* 
*         SINCE THE TYPE 3 CALLER OFTEN DOES NOT KNOW HOW (OR IS NOT
*         ALLOWED) TO DETERMINE IF THE MEMORY RESOURCES ARE ALREADY 
*         AVAILABLE FOR REASSIGNMENT, PAUSEIO CAN BE CALLED CASUALLY, 
*         AND WILL NO-OP ITSELF IF THE DESIRED CONDITIONS ARE ALREADY 
*         TRUE. 
* 
*         PAUSEIO ALSO ZEROES OUT FETCHGUIDE.  THIS IS USED TO CONTROL
*         THE QUANTITY OF DATA PREFETCHED AS A FUNCTION OF HOW MUCH 
*         SUCCESS WE HAVE HAD IN UTILIZING PREFETCHED DATA IN RECENT
*         HISTORY.  A PAUSEIO CALL IS REGARDED AS AN ADMISSION THAT 
*         ANY CURRENT PREFETCHING HAS LOST ALL RELEVANCE. 
* 
*         ENTRY  IOREAD - WHETHER WE WERE PREVIOUSLY TRYING TO READ 
*                    SOMETHING WHICH MUST NOW BE THROWN AWAY. 
*                IOWRITE - WHETHER WE HAD PREVIOUSLY REQUESTED THAT 
*                    SOMETHING BE WRITTEN, WHICH MUST NOW BE BROUGHT
*                    TO FASTEST POSSIBLE COMPLETION.
*                RDIN, RDOUT - BRACKET ACTIVE SEGMENT OF READ LIST. 
*                FETIN, FETOUT - BRACKET CIRCULAR BUFFER DATA.
* 
*         EXIT   IOREAD, IOWRITE, IOACT - GUARANTEED FALSE. 
*                FET - CIRCULAR BUFFER GUARANTEED EMPTY.
*                RDLIST[ALL], RDIN, RDOUT - GUARANTEED EMPTY. 
*                FETCHGUIDE - ZEROED. 
* 
*         CALLS  WAIT, MOVEIO.
# 
  ITEM I, J, K; 
  
  FETCHGUIDE=0; 
  
  IF IOREAD THEN
    BEGIN 
    WAIT;                    # FOR ACTIVE TRANSFER TO FINISH         #
    FOR I = RDIN WHILE I NQ RDOUT DO   # CLEAN OUT READLIST          #
      BEGIN 
      I = I - 1;
      IF I LS 0 THEN I = RDLIM - 1; 
      RDLIST[I] = 0;
      END 
    FETOUT = FETIN;          # ABANDON CIRCULAR BUFFER CONTENT       #
    IOACT = FALSE;
    IOREAD = FALSE; 
    END 
  
  ELSE IF IOWRITE THEN
    BEGIN 
    WHYLE IOWRITE DO
      BEGIN 
      MOVEIO; 
      WAIT; 
      END 
    END 
  
  IOEND 
PAGE
PROC SETREAD((DA)); 
  BEGIN 
# 
**        SETREAD - ADD DISK ADDRESS TO READ LIST.
* 
*         SETREAD PERFORMS FET INITIALIZATION AS NEEDED, TO ASSURE
*         THAT THE BUFFER AND FET ARE TURNED AROUND TO THE INPUT
*         DIRECTION, AND APPENDS THE SPECIFIED DISK ADDRESS ONTO THE
*         READ LIST.  THE CALLER IS RESPONSIBLE TO ASSURE THAT
*         SETREAD WILL NOT ENCOUNTER A FULL READ LIST.  THE CALLER IS 
*         ALLOWED TO CALL SETREAD FOR AN ADDRESS ALREADY LISTED, IN 
*         WHICH CASE SETREAD NO-OPS ITSELF.  THE USER IS REQUIRED TO
*         PROVIDE AN IN-BOUNDS DISK ADDRESS, AND TO HAVE USED PAUSEIO 
*         TO COMPLETE ANY PREVIOUS OUTPUT ACTIVITIES. 
* 
*         ENTRY  DA - SECTOR NUMBER FOR DESIRED PRU.
*                IOREAD - WHETHER FET ALREADY INITIALIZED FOR INPUT.
*                IOWRITE, MAXDA - WHEN CONSISTENCY CHECKING IS ENABLED, 
*                    THESE ARE USED TO VERIFY VALIDITY. 
*                RDIN, RDOUT - BRACKET ACTIVE SEGMENT OF READ LIST. 
*                RDLIST[ALL] - SETUP FOR EXISTING DISK ADDRESSES
*                    AND WITH ZEROES IN EXCESS LIST AREAS.
* 
*         EXIT   IOREAD, IOACT - TRUE.
*                FET AND CIRCULAR BUFFER - VALID FOR MOVEIO CALL. 
*                RDIN, RDOUT - UPDATED FOR ENLARGED READ LIST.
*                RDLIST[NEW RDIN-1] - DISK ADDRESS STORED HERE. 
* 
*         CALLS  TRAGIC, INITFET. 
# 
  ITEM DA;
  ITEM P; 
  
  CONTROL IFGQ PARANOIA,5;
    IF DA LQ 0 OR DA GR MAXDA THEN
      BEGIN 
      TRAGIC(" OUT OF BOUNDS PRU ADDRESS ON READ (1).$"); 
      END 
    IF IOWRITE THEN TRAGIC(" WORKFILE BUFFER FULL ON READ.$");
  CONTROL FI; 
  
  IF NOT IOREAD THEN
    BEGIN 
    INITFET(DISK,DSKSIZ); 
  
    RDIN = 0; 
    RDOUT = 0;
    FETLAW = LOC(RDLIST[0]);
  
    IOACT = TRUE; 
    IOREAD = TRUE;
    END 
  
  FOR P = RDOUT WHILE RDLIST[P] NQ DA DO
    BEGIN 
    IF P EQ RDIN THEN 
      BEGIN 
      RDLIST[RDIN] = DA;
      RDIN = RDIN + 1;
      IF RDIN EQ RDLIM THEN RDIN = 0; 
      CONTROL IFGQ PARANOIA,5;
        IF RDIN EQ RDOUT THEN TRAGIC(" RPHRLS LIST BUFFER IS FULL.$");
      CONTROL FI; 
      END 
  
    ELSE
      BEGIN 
      P = P + 1;
      IF P EQ RDLIM THEN P = 0; 
      END 
  
    END 
  
  END 
PAGE
PROC PREFETCH((DD));
  BEGIN 
# 
**        ARRANGE READ LIST FOR SEVERAL PREFETCHES. 
* 
*         PREFETCH SHOULD BE CALLED ONCE PER SECTOR PROCESSED, BY 
*         THOSE PROCESSES WHICH ACT UPON POTENTIALLY LARGE SERIES OF
*         DISK SECTORS IN EITHER THE FORWARD OR BACKWARD LOGICAL
*         ORDER OF TEXT IN THE FILE.  THE LOGICAL ORDER CAN BE QUITE
*         DIFFERENT FROM THE PHYSICAL ORDER DUE TO THE TREE LINKAGES. 
* 
*         PREFETCH DETERMINES SEVERAL DISK ADDRESSES WHOSE NEED IS
*         ANTICIPATED ON BEHALF OF THE CALLER, AND USES SETREAD TO
*         ARRANGE THAT SUCH SECTORS CAN BE READ IN THE LOGICAL ORDER. 
*         IT IS THE CALLING PROCESSES RESPONSIBILITY TO OCCASIONALLY
*         CALL MOVEIO FOR ACTUAL INITIATION OF CIO. 
* 
*         TO MINIMIZE OVERHEAD, PREFETCH CHOOSES A DEPTH OF 
*         PREFETCHING BASED ON APPARENT RECENT SUCCESS IN UTILIZING 
*         PREFETCHED DATA, AND SUCCESSIVELY UPDATES THE DEPTH OF
*         PREFETCH.  THIS DEPTH INDICATOR MAY BE CLEARED BY PAUSEIO 
*         IN RECOGNITION OF THE ABANDONMENT OF A READ, OR BY THE
*         CALLER WHEN A LOGICAL BREAK IN SEQUENTIAL PROCESSING IS 
*         KNOWN TO OCCUR. 
* 
*         TO MINIMIZE OVERHEAD, PREFETCH ATTEMPTS TO PRODUCE EITHER 
*         ZERO OR SEVERAL NEW ENTRIES IN THE READ LIST, AND ATTEMPTS
*         TO NO-OP ITSELF WHEN IT IS RECOGNIZED THAT ONLY ONE OR A
*         FEW ENTRIES COULD BE PRODUCED.  PREFETCH MUST KEEP THE
*         DEPTH OF PREFETCHING WITHIN THE LEGAL BOUNDS OF THE SETREAD 
*         ROUTINE.
* 
*         THE DEPTH OF PREFETCHING IS LIMITED TO THE SMALLEST OF THE
*         FOLLOWING FACTORS - (1) THE FETCHGUIDE QUANTITY, WHICH
*         REPRESENTS RECENT SUCCESS, (2) THE LEGAL LIMITS IMPOSED BY
*         SETREAD, AND (3) ONE AND A HALF TIMES THE SIZE OF THE 
*         CIRCULAR BUFFER.  PREFETCHING IS SET UP ONLY WHEN THE 
*         EXISTING QUEUE IS LESS THAN HALF THE DESIRED DEPTH.  FOR
*         LARGER QUEUES, IT IS ANTICIPATED THE QUEUE WILL BE SHRUNK 
*         BACK DOWN AT SOME FUTURE CALL TO PREFETCH, AT WHICH TIME
*         A LARGE BATCH OF QUEUE ENTIRES CAN BE PRODUCED. 
* 
*         ONCE PREFETCH DECIDES TO SET UP A BATCH OF READ LIST QUEUE
*         ENTRIES, IT IDENTIFIES THE SECTORS TO BE READ AS ALL DATA 
*         (BOTTOM) LEVEL PRUS, POINTED TO BY THE NEXT-TO-BOTTOM 
*         DIRECTORY, LOGICALLY ADJACENT TO THE CURRENT DATA SECTOR IN 
*         THE SPECIFIED DIRECTION.  PREFETCH ALSO CAN QUEUE THE NEXT
*         LOGICALLY ADJACENT NEXT-TO-BOTTOM DIRECTORY, BY LOOKING 
*         ANOTHER LEVEL TOWARDS THE ROOT OF THE TREE. 
* 
*         ENTRY  FETCHGUIDE - CURRENT GUIDELINE FOR DEPTH.
*                RDIN, RDOUT - DESCRIBE CURRENT READ LIST.
*                DSKSIZ - CIRCULAR BUFFER SIZE. 
*                PALVL - DEEPEST TREE LEVEL CURRENTLY VALID.
*                PACACHE[ALL] - CACHE FRAMES AT EACH TREE LEVEL.
*                BFPRU[ALL] - DISK ADDRESSES FOR EACH FRAME.
*                PAFINAL[ALL] - LAST WORDS IN EACH SECTOR OF TREE.
*                IOWRITE - WHETHER FET INTERLOCKED FOR OUTPUT.
* 
*         EXIT   RDLIST[ALL], RDIN, RDOUT - UPDATED.
*                FETCHGUIDE - INCREMENTED UNLESS DIRECTORY INCLUDED.
* 
*         CALLS  LSTAVAIL, LSTUSAGE, MIN, MAX, SETREAD. 
* 
*         USES   P<PRU> WITH RESTORATION. 
# 
  ITEM DD,N;
  ITEM DSTOP; 
  ITEM DONE B;
  ITEM TMPPRU;
  ITEM TMPPL,TMPD;
  ITEM I; 
  
  IF FETCHGUIDE LS 0 OR IOWRITE THEN RETURN;
  
  FETCHGUIDE=FETCHGUIDE+2;
  N=LSTAVAIL; 
  N=MIN(N-3,3*DSKSIZ/O"200"); 
  N=MIN(N,FETCHGUIDE);
  
  IF LSTUSAGE LQ N/2 THEN 
    BEGIN 
    TMPPRU = P<PRU>;
  
    DONE = FALSE; 
    FOR TMPPL = PALVL-1 STEP -1 WHILE TMPPL GQ 0 AND NOT DONE DO
      BEGIN 
      TMPD = PADISP[TMPPL] + DD;
      P<PRU> = LOC(BFPRU[PACACHE[TMPPL]]);
  
      IF DD GR 0 THEN DSTOP = MIN(PAFINAL[TMPPL]+1,TMPD+N); 
      ELSE DSTOP = MAX(0,TMPD-N); 
  
      IF DSTOP EQ TMPD+N*DD THEN DONE = TRUE; 
      ELSE FETCHGUIDE=-1; 
      N=1;
  
      FOR TMPD = TMPD STEP DD WHILE TMPD NQ DSTOP DO
        BEGIN 
        FOR I=0 STEP 1 UNTIL MAXCACHE DO
          BEGIN 
          IF BFDA[I] EQ DIRDA[TMPD] THEN TEST TMPD; 
          END 
        SETREAD(DIRDA[TMPD]); 
        END 
  
      END 
  
    P<PRU> = TMPPRU;
    END 
  END 
PAGE
PROC READPA;
  IOBEGIN(READPA) 
# 
**        READPA - READ NEXT DEEPER TREE PATH SECTOR. 
* 
*         READPA IS USED TO ACCESS THE SECTOR OF DIRECTORY OR DATA
*         FOR A SPECIFIED LEVEL OF THE TREE.  THE HIGHER LEVELS OF
*         THE TREE MUST ALREADY BE PROPERLY FILLED IN.  ANY RESIDUAL
*         SECTOR IN THE CURRENT TREE LEVEL MUST BE PROPERLY RELEASED
*         FROM OWNERSHIP, SO THAT READPA WILL NOT NEED TO CONCERN 
*         ITSELF WITH THE UPDATING OF ALTERED DATA TO DISK.  THE
*         CALLER DOES NOT NEED TO SETUP THE FET AND CIRCULAR BUFFER 
*         FOR INPUT, AND READPA WILL TAKE CARE OF THAT IF NEEDED. 
* 
*         WHEN POSSIBLE, READPA WILL ACCESS THE DESIRED DISK SECTOR 
*         BY SIMPLY IDENTIFYING IT IN CACHE AND CLAIMING OWNERSHIP OF 
*         THE RIGHT CACHE FRAME.  WHEN THE CACHE DOES NOT PROVIDE THE 
*         NECESSARY DATA, READPA WILL READ IT FROM DISK.  IF THE FET
*         AND CIRCULAR BUFFER ARE PREVIOUSLY INTERLOCKED FOR PENDING
*         OUTPUT, READPA WILL ASSURE THAT THE UNFINISHED WRITES GET 
*         DONE.  IF THE FET IS ALREADY SETUP FOR INPUT BUT HAS THE
*         WRONG DISK ADDRESS COMING IN AT THE FRONT OF THE CIRCULAR 
*         BUFFER, READPA WILL DISCARD SUCH INCORRECT INPUT AND
*         REINITIALIZE TO INPUT THE RIGHT DISK ADDRESS.  IF THE FET 
*         IS ALREADY SET UP FOR INPUT WITH THE RIGHT SECTOR IN OR 
*         COMING INTO THE CIRCULAR BUFFER, READPA WILL MAKE USE OF
*         THAT CIO ACTIVITY.
* 
*         IF READPA READS THE SECTOR FROM DISK, IT CLEANS UP THE
*         RANDOM READ LIST AFTERWARDS, ATTEMPTING TO SHUFFLE THE
*         ACTIVE SEGMENT OF THE READ LIST TO MAXIMIZE THE REMAINING 
*         LIST CAPACITY BEFORE WRAPAROUND.  READPA ALSO CHECKS TO SEE 
*         IF THE BUFFER IS SUFFICIENTLY EMPTY WITH A SUFFICIENTLY 
*         FULL PREFETCH LIST SO THAT ANTICIPATORY READS SHOULD BE 
*         STARTED.
* 
*         ENTRY  TEMP - SECTOR NUMBER.
*                PL - CURRENT PATH LEVEL. 
*                PACACHE[ALL] - CACHE FRAMES CURRENTLY OWNED. 
*                CACHEOWNED[ALL] - SETUP. 
*                BFDA[ALL] - IDENTIFY SECTOR ADDRESSES EACH FRAME.
*                BFHEAD[ALL] - HEADER WORDS FOR EACH CACHED SECTOR. 
*                ACCTPRU - PRU TRANSFER COUNT.
*                IOWRITE - WHETHER FET ALREADY SET FOR WRITES.
*                RDIN, RDOUT - READ LIST BRACKETING.
*                RDLIST[ALL] - SETUP. 
*                IOREAD - WHETHER SOME READ ALREADY SCHEDULED.
*                FETCHGUIDE - PREFETCH QUANTITY GUIDELINE.
*                QUARTER - APPROX ONE FOURTH BUFFER SIZE. 
* 
*         EXIT   PACACHE[PL] - INDICATES CACHE FRAME WITH SECTOR. 
*                CACHEOWNED[PACACHE[PL]] - TRUE.
*                ANY OLD CACHE FRAMES MAY HAVE BEEN FLUSHED TO
*                    DISK AND/OR REASSIGNED IN MEMORY.
*                PAHEAD[PL] - HEADER WORD.
*                IOWRITE - FALSE IF NOT CACHE HIT.
*                IOREAD - POSSIBLY CHANGED. 
*                RDOUT - ADVANCED IF DISK READ DONE.
*                RDIN - RDIN AND RDOUT POSSIBLY RELOCATED IF READ 
*                    DONE AND CIO TERMINATED. 
*                ACCTPRU - INCREMENTED IF NO CACHE HIT. 
*                FETCHGUIDE - POSSIBLY NEW VALUE. 
*                ANTICIPATORY CIO POSSIBLY CALLED.
* 
*         CALLS  TRAGIC, COUNTCACHE, FLUSHCACHE, DISOWNCACHE, 
*                WIOSTAT, PAUSEIO, ALLOCCACHE, SETREAD, MOVEIO, 
*                DELAY, RECALL, RDWBUF, BUFUSAGE. 
* 
*         USES   P<TOO>.
# 
  ITEM X1;
  
  CONTROL IFGQ PARANOIA,5;
    IF PADIRTY[PL] THEN TRAGIC(" ALTERED PRU WAS NOT REWRITTEN.$"); 
  CONTROL FI; 
  
  IF COUNTCACHE LQ 2 THEN FLUSHCACHE; 
  
  # FIRST RELEASE CURRENTLY OWNED SECTOR IF ANY # 
  DISOWNCACHE(PACACHE[PL]); 
  
  FOR X1=0 STEP 1 UNTIL MAXCACHE DO 
    BEGIN                     # TRY TO FIND IN CACHE        # 
    IF BFDA[X1] EQ TEMP THEN        # GOT IT  # 
      BEGIN 
      CONTROL IFGQ PARANOIA,5;
        IF CACHEOWNED[X1] THEN
          BEGIN 
          TRAGIC(" WORKFILE BUFFER ALLOCATED TWICE.$"); 
          END 
      CONTROL FI; 
      PACACHE[PL]=X1; 
      CACHEOWNED[X1]=TRUE;
      PAHEAD[PL] = BFHEAD[X1];
      CONTROL IFEQ MULTI,1; 
        WIOSTAT(1);          # ACCUMULATE CACHE HITS #
      CONTROL FI; 
      IORET 
     END
   END
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(2);              # ACCUMULATE SECTORS READ #
    ACCTPRU=ACCTPRU+1;
  CONTROL FI; 
  
  IF IOWRITE THEN            # DO ANY PENDING WRITES       #
    BEGIN 
    PAUSEIO;
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(3);            # ACCUMULATE FLUSHED WRITES #
    CONTROL FI; 
    END 
  
  CONTROL IFGQ PARANOIA,5;
    IF TEMP LQ 0 OR TEMP GR MAXDA THEN
      BEGIN 
      TRAGIC(" OUT OF BOUNDS PRU ADDRESS ON READ (2).$"); 
      END 
  CONTROL FI; 
  
  PADA[PL] = TEMP;
  ALLOCCACHE; 
  
  IF IOREAD AND RDLIST[RDOUT] NQ TEMP THEN
    BEGIN 
    PAUSEIO;
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(4);            # ACCUMULATE READ REJECTS #
    CONTROL FI; 
    END 
  
  IF NOT IOREAD THEN
    BEGIN 
    SETREAD(TEMP);
    FETCHGUIDE=2; 
    END 
  
  WHYLE FETIN EQ FETOUT DO
    BEGIN 
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(10);           # ACCUMULATE NOT ALREADY IN BUFFER # 
    CONTROL FI; 
    MOVEIO; 
    CONTROL IFEQ MULTI,1; 
      WAIT; 
    CONTROL FI; 
    CONTROL IFEQ SINGLE,1;
      IF NOT FETDUN THEN DELAY; 
    CONTROL FI; 
    END 
  
  P<TOO> = LOC(BFPRU[PACACHE[PL]]); 
  RDWBUF(O"100"); 
  PAHEAD[PL] = BFHEAD[PACACHE[PL]]; 
  
  CONTROL IFGQ PARANOIA,5;
    IF TEMP NQ RDLIST[RDOUT] THEN 
      BEGIN 
      TRAGIC(" RANDOM READ LIST INCORRECT.$");
      END 
    IF PADA[PL] NQ TEMP THEN TRAGIC(" PRU CONTENT INCORRECT.$");
    IF PADIRTY[PL] THEN 
      BEGIN 
      TRAGIC(" STATUS FLAGS SHOULD HAVE BEEN CLEARED.$"); 
      END 
  CONTROL FI; 
  
  RDLIST[RDOUT] = 0;
  IF RDOUT EQ RDLIM-1 THEN RDOUT = 0; 
  ELSE RDOUT = RDOUT + 1; 
  IF FETDUN AND RDIN EQ RDOUT THEN
    BEGIN 
    RDIN=0; 
    RDOUT=0;
    FETCHGUIDE=ABS(FETCHGUIDE); 
    END 
  
  IF BUFUSAGE LQ QUARTER THEN MOVEIO; 
  
  IOEND 
PAGE
PROC ALLOC; 
  BEGIN 
# 
**        ALLOC - ASSIGN DISK ADDRESS.
* 
*         ALLOC IS CALLED WHEN A NEW SECTOR MUST BE CREATED.  THE 
*         NEXT PRU OFF THE LOGICAL END OF THE FILE IS CHOSEN.  NOTE 
*         THAT THE LOGICAL END OF THE FILE REPRESENTS THOSE ADDRESSES 
*         WHICH HAVE BEEN ALLOCATED.  THE PHYSICAL END OF THE FILE
*         REPRESENTS THOSE ADDRESSES ACTUALLY WRITTEN ONTO THE FILE,
*         AND CAN BE A SMALLER NUMBER THAN THE LOGICAL END. 
* 
*         ENTRY  PL - CURRENT PATH LEVEL. 
*                DANEXT - ALLOCATION THRESHOLD. 
* 
*         EXIT   PADA[PL] - SETUP.
*                DANEXT - INCREMENTED.
* 
*         CALLS  TRAGIC.
# 
  CONTROL IFGQ PARANOIA,5;
    IF PADIRTY[PL] THEN TRAGIC(" OLD SECTOR MUST BE WRITTEN.$");
  CONTROL FI; 
  PADA[PL] = DANEXT;
  DANEXT = DANEXT + 1;
  END 
  
  
PROC DEALLOC; 
  BEGIN 
# 
**        DEALLOC - DISCARD DISK SECTOR.
* 
*         DEALLOC IS USED TO DISCARD A DISK SECTOR WHICH CAN NO 
*         LONGER BE USED.  THIS CAN BE NEEDED WHEN A SECTOR HAS 
*         OVERFLOWED AND HAS BEEN SPLIT INTO TWO NEW SECTORS.  IF THE 
*         ROOT SECTOR NEEDS TO BE SPLIT, A COMPLETE NEW TREE LEVEL IS 
*         CREATED WITH NEW SECTORS, THE ROOT SECTOR IS MODIFIED TO
*         BECOME A DIRECTORY FOR THE NEW LEVEL, AND NO DEALLOCATION 
*         IS NEEDED.  IF A NON-ROOT SECTOR NEEDS TO BE SPLIT EXACTLY
*         AT (JUST AFTER) ITS EXISTING LAST FIELD, THEN THAT SECTOR 
*         IS PRESERVED WITHOUT DEALLOCATION AND A NEW SECTOR IS 
*         CREATED FOR THE OVERFLOW CONTENT.  BUT IF A NON-ROOT SECTOR 
*         MUST BE SPLIT SOMEWHERE IN THE MIDDLE OF ITS EXISTING 
*         CONTENT, IT MUST BE DEALLOCATED AND BE REPLACED BY TWO
*         COMPLETELY NEW SECTORS. 
* 
*         THE REASON FOR DEALLOCATION IS THAT NOTHING MUST BE ALLOWED 
*         TO BE POINTED TO UNTIL AND UNLESS IT ALREADY EXISTS, AS 
*         UPDATED TO DISK ALTHOUGH NOT NECESSARILY AS CACHED IN 
*         MEMORY.  SINCE A NON-ROOT SECTOR IS POINTED TO BE 
*         HIGHER-LEVEL DIRECOTRIES, IT CANNOT BE TOTALLY REFORMATTED
*         IN PLACE, THEREFORE IT IS LEFT AS IS AND NEW SECTORS
*         REPLACE IT.  THE DIRECTORIES WHICH POINT TO THE DISCARDED 
*         SECTOR WILL EVENTUALLY BE UPDATED THEMSELVES.  UNTIL THEN,
*         THOSE HIGHER DIRECTORIES MAY BE OUT OF DATE BUT AT LEAST
*         POINT TO A VALID DATA STRUCTURE.
* 
*         ENTRY  PL - CURRENT TREE LEVEL. 
* 
*         EXIT   PADA[PL] - CLEARED AWAY. 
  
* 
*         CALLS  TRAGIC.
# 
  CONTROL IFGQ PARANOIA,5;
    IF PL EQ 0 OR PADA[PL] EQ 0 THEN
      BEGIN 
      TRAGIC(" SECTOR CANNOT BE DEALLOCATED.$");
      END 
  CONTROL FI; 
  PADA[PL] = 0; 
  PADIRTY[PL] = FALSE;
  END 
  
  
PROC SETWRITE(PARM);
  BEGIN 
# 
**        SETWRITE - SCHEDULE EXTENSION/ALTERATION OF FILE. 
* 
*         SETWRITE INITIALIZES THE FET FOR OUTPUT.  IT SETS THE IOACT 
*         FLAG TO INDICATE THAT I/O IS SCHEDULED, SETS IOWRITE TO 
*         INDICATE THAT THE I/O SHALL BE AN OUTPUT FUNCTION, AND
*         SETS OR CLEARS IOAPEND ACCORDING TO WHETHER THE SECTOR TO 
*         BE WRITTEN IS WITHIN THE CURRENT PHYSICAL BOUNDARIES OF THE 
*         FILE OR REPRESENTS AN EXTENSION OF PHYSICAL SIZE. 
* 
*         ENTRY  PARM - DISK ADDRESS TO BE WRITTEN. 
*                MAXDA - CURRENT PHYSICAL SIZE OF FILE. 
* 
*         EXIT   FET - INITIALIZED WITH EMPTY CIRCULAR BUFFER.
*                IOACT - TRUE.
*                IOWRITE - TRUE.
*                IOAPEND - TRUE OR FALSE BASED ON PARM AND MAXDA. 
*                LASTDA - SET TO KEEP TRACK OF WHERE WE ARE IN FILE.
*                FETRR - SETUP. 
* 
*         CALLS  INITFET. 
# 
  ITEM PARM;
  
  INITFET(DISK,DSKSIZ); 
  
  IOACT = TRUE; 
  IOWRITE = TRUE; 
  
  IF PARM GR MAXDA THEN 
    BEGIN 
    IOAPEND = TRUE; 
    FETRR = LOC(DUMB);
    END 
  
  ELSE
    BEGIN 
    IOAPEND = FALSE;
    FETRR = PARM; 
    LASTDA = PARM - 1;
    END 
  
  END 
PAGE
PROC TRYWRITE(PARM,FLAG); 
  BEGIN 
# 
**        TRYWRITE - TRANSMIT SECTOR TO CIRCULAR BUFFER.
* 
*         TRYWRITE ATTEMPTS TO TRANSMIT A SECTOR TO THE CIRCULAR
*         BUFFER.  IT MAY FAIL TO DO SO IF THE CIRCULAR BUFFER ALREADY
*         CONTAINS SECTORS FOR WHICH THIS SECTOR IS NOT CONTIGUOUS. 
*         THE CALLER SHOULD CALL PAUSEIO WHEN TRYWRITE FAILS, AND 
*         THEN CALL THIS ROUTINE AGAIN.  TRYWRITE CANNOT FAIL IF
*         PAUSEIO IS USED.
* 
*         ENTRY  P<FROM> - POINTS TO TEXT OF SECTOR.
*                PARM - DISK ADDRESS. 
*                MAXDA - PHYSICAL FILE SIZE.
*                LASTDA - MOST RECENT DISK ADDRESS TRANSMITTED. 
* 
* 
*         EXIT   P<FROM> - POSSIBLY DESTROYED.
*                MAXDA - INCREMENTED IF FILE EXTENDED.
*                LASTDA - INCREMENTED IF CONTIGUOUS EARLIER OUTPUT. 
*                FET - GUARANTEED SETUP FOR OUTPUT WITH THIS SECTOR 
*                    OF TEXT IN CIRCULAR BUFFER.  EXISTING FET SETUP
*                    AND CIRCULAR BUFFER CONTENT, IF ANY, ARE NOT 
*                    DISTURBED WITH REGARD TO EARLIER OUTPUT. 
*                FLAG - TRUE IF THIS SECTOR TRANSMITTED.  FALSE IF
*                    UNABLE TO TRANSMIT DUE TO DISCONTIGUITY WITH 
*                    EARLIER OUTPUT.
*                IOACT, IOWRITE - TRUE. 
*                IOAPEND - SETUP. 
* 
*         CALLS  SETWRITE, WTWBUF.
# 
  ITEM PARM, FLAG B, ZERO = 0;         # ZERO IS A CONSTANT          #
  
  FLAG = TRUE;
  
  IF NOT IOWRITE THEN SETWRITE(PARM); 
  
  IF IOAPEND THEN 
    BEGIN 
  
    IF MAXDA+1 LS PARM THEN  # ADD PADDING SECTOR          #
      BEGIN 
      MAXDA = MAXDA + 1;
      P<FROM>=LOC(ZERO);
      WTWBUF(O"100"); 
      END 
  
    ELSE IF MAXDA+1 EQ PARM THEN       # ADD REAL SECTOR   #
      BEGIN 
      MAXDA = MAXDA+1;
      WTWBUF(O"100"); 
      END 
  
    ELSE
      BEGIN 
      FLAG = FALSE; 
      END 
    END 
  
  ELSE
    BEGIN 
  
    IF LASTDA+1 EQ PARM AND PARM LQ MAXDA THEN
      BEGIN 
      LASTDA = LASTDA+1;
      WTWBUF(O"100"); 
      END 
  
    ELSE
      BEGIN 
      FLAG = FALSE; 
      END 
    END 
  
  END 
PAGE
PROC FLUSHCACHE;
  IOBEGIN(FLUSHCACHE) 
# 
**        FLUSHCACHE - UPDATE ALTERED CACHE FRAMES TO DISK. 
* 
*         FLUSHCACHE IS OBLIGATED TO ASSURE THAT ALL ALTERED, UNOWNED 
*         CACHE PAGES BECOME UNALTERED IN CACHE.  THIS REQUIRES THAT
*         SUCH PAGES BE TRANSMITTED TO THE CIRCULAR BUFFER.  THIS IN
*         TURN CAN BE DONE ONLY IN ORDER OF AGE AND IN ADDRESS ORDER. 
* 
*         UNALTERED FRAMES DO NOT NEED TO BE UPDATED TO DISK.  OWNED
*         FRAMES CANNOT YET BE UPDATED - IN THE AGE-RESTRICTED ORDER
*         OF UPDATE, OWNED PAGES CAN BE THOUGHT OF AS HAVING THE
*         WORST AGE CATEGORY. 
* 
*         EACH SECTOR CAN BE TRANSMITTED TO THE CIRCULAR BUFFER IF
*         THE BUFFER AND FET ARE AVAILABLE FOR OUTPUT, AND IF THE 
*         BUFFER IS EITHER EMPTY OR HAS ONE OR MORE SECTORS ALREADY IN
*         IT FOR WHICH THE NEW SECTOR IS CONTIGOUS IN ADDRESS.  IF THE
*         FET IS ALREADY CLAIMED FOR INPUT PROCESSING, PAUSEIO CAN BE 
*         CALLED TO STOP AND DISCARD SUCH INPUT.  FOR SPANNED 
*         ADDRESSES, WE MUST CALL PAUSEIO.  THIS LOGIC IS PROVIDED BY 
*         INSPECTING THE FLAG RETURNED BY TRYWRITE, AND CALLING 
*         TRYWRITE A SECOND TIME WITH PAUSEIO, WHEN TRYWRITE FAILED.
* 
*         BECAUSE THE ALLOCATION OF NEW SECTORS AT THE LOGICAL END OF 
*         THE FILE CAN RUN SEVERAL UNITS AHEAD OF THE ACTUAL
*         EXTENDING OF THE PHYSICAL END OF THE FILE, AND BECAUSE THE
*         AGE-DEPENDENT ORDERING MIGHT REQUIRE A HIGH ADDRESS SECTOR
*         TO BE WRITTEN BEFORE A LOWER ADDRESS SECTOR MAY BE LEGALLY
*         WRITTEN, A SITUATION CAN ARISE WHERE A SECTOR MUST BE 
*         WRITTEN SEVERAL ADDRESSES BEYOND THE CURRENT PHYSICAL END 
*         OF FILE.  WHEN THIS OCCURS, FLUSHCACHE RECOGNIZES THE 
*         SITUATION AND PADS THE PHYSICAL END OUT TO WITHIN ONE 
*         SECTOR OF THE SECTOR THAT MUST BE NEXT WRITTEN.  THE
*         INTERMEDIATE SECTORS THUS GET A DISK IMAGE WHICH IS NOT UP
*         TO DATE IN ANY WAY WHATSOEVER.  THIS IS NOT A PROBLEM - THE 
*         UP TO DATE FRAMES FOR SUCH PADDING WILL EVENTUALLY BE 
*         RELEASED FOR ACTUAL DISK UPDATE.  THE CONTENT OF PADDING
*         SECTORS IS INNOCUOUS, TO FOLLOW THE RULE THAT NOTHING CAN 
*         BE POINTED TO ON DISK UNTIL THE OBJECT ITSELF IS UP TO DATE 
*         ON DISK.
* 
*         ENTRY  CACHEDIRTY[ALL] - SETUP. 
*                CACHEAGE[ALL] - SETUP. 
*                CACHEOWNED[ALL] - SETUP. 
*                BFDA[ALL] - SETUP. 
*                IOREAD - WHETHER OBSOLETE INPUT SCHEDULED. 
*                IOACT - WHETHER ANY PREVIOUS I/O SCHEDULED.
*                FETDUN - FET INTERLOCK.
*                IOAPEND - WHETHER A PREVIOUS WRITE EXTENDS.
*                QUARTER - APPROX ONE FOURTH BUFFER CAPACITY. 
*                ACCTPRU - SECTOR TRANSFER ACCUMULATOR. 
*                MAXDA - FILE PHYSICAL BOUNDARY.
* 
*         EXIT   ALL ALTERED AND UNOWNED CACHE FRAMES OUTPUT TO DISK
*                    OR BUFFERED FOR OUTPUT.
*                IOREAD - FORCED FALSE IF ANY OUTPUT NEEDED.
*                IOACT, IOWRITE, IOAPEND - POSSIBLY TRUE. 
*                CACHEDIRTY[ALL] - FALSE WHERE CACHEOWNED IS FALSE. 
*                ACCTPRU - INCREMENTED FOR ANY WORK DONE. 
*                MAXDA - INCREMENTED IF FILE EXTENDED.
* 
*         CALLS  WIOSTAT, PUSHTEMP, POPTEMP, PAUSEIO, BUFAVAIL, 
*                MOVEIO, WAIT, DELAY, TRYWRITE, TRAGIC. 
* 
*         USES   P<FROM>, TEMP(RESTORED). 
# 
  ITEM TMP1, TMP2, BOOL B;
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(5);              # ACCUMULATE CACHE FLUSHES # 
  CONTROL FI; 
  
  PUSHTEMP; 
  
FLUSHLOOP:  
  
  TMP1=999999999; 
  TEMP=-1;
  FOR TMP2=0 STEP 1 UNTIL MAXCACHE DO 
    BEGIN                     # SEARCH FOR OLDEST DIRTY     # 
    IF CACHEDIRTY[TMP2] AND CACHEAGE[TMP2] LS TMP1
      AND NOT CACHEOWNED[TMP2] THEN 
      BEGIN 
      TEMP=TMP2;
      TMP1=CACHEAGE[TMP2];
      END 
    END 
  IF TEMP LS 0 THEN          # NO MORE FLUSH NEEDED        #
    BEGIN 
    POPTEMP;
    IORET 
    END 
  
  CACHEDIRTY[TEMP]=FALSE; 
  IF BFDA[TEMP] EQ 0 THEN GOTO FLUSHLOOP; 
  
  IF IOREAD THEN             # COMPLETE ANY INPUT          #
    BEGIN 
    PAUSEIO;
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(6);            # ACCUMULATE READ REJECTS #
    CONTROL FI; 
    END 
  
WRITELOOP:  
  
  WHYLE IOACT AND BUFAVAIL LS O"100" DO 
    BEGIN                     # NEED ROOM IN BUFFER         # 
    MOVEIO; 
    CONTROL IFEQ MULTI,1; 
      WAIT; 
    CONTROL FI; 
    CONTROL IFEQ SINGLE,1;
      IF NOT FETDUN THEN DELAY; 
    CONTROL FI; 
    END 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(7);              # ACCUMULATE SECTORS WRITTEN # 
    ACCTPRU=ACCTPRU+1;
  CONTROL FI; 
  
  P<FROM>=LOC(BFPRU[TEMP]); 
  TRYWRITE(BFDA[TEMP],BOOL);
  IF NOT BOOL THEN
    BEGIN                     # ONE FAILURE ALLOWED, NOT TWO          # 
    PAUSEIO;
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(8);            # ACCUMULATE DISCONTIGUOUS WRITES #
    CONTROL FI; 
    P<FROM>=LOC(BFPRU[TEMP]); 
    TRYWRITE(BFDA[TEMP],BOOL);
    IF NOT BOOL THEN TRAGIC(" UNABLE TO WRITE FROM BUFFER.$");
    END 
  
  # EITHER ADD MORE PADDING FOR THIS FLUSH OR FLUSH ANOTHER # 
  IF IOAPEND AND MAXDA LS BFDA[TEMP] THEN 
    BEGIN 
    CONTROL IFEQ MULTI,1; 
      WIOSTAT(9);            # ACCUMULATE PADDING SECTORS # 
    CONTROL FI; 
    GOTO WRITELOOP; 
    END 
  IF BUFAVAIL LQ QUARTER THEN MOVEIO; 
  GOTO FLUSHLOOP; 
  
  IOEND                       # OF FLUSHCACHE       # 
  
  
PROC CLOSEOUT;
  IOBEGIN(CLOSEOUT) 
# 
**        CLOSEOUT - FINISH UPDATE OF DISK. 
* 
*         WORKFILE CLOSEOUT CONDITIONS HAVE BEEN DETECTED.  UNDER 
*         CLOSEOUT CONDITIONS, WE MUST WRITE NOT ONLY THE ZERO-LEVEL
*         SECTOR, BUT ALSO THE DATA SEGMENT (RIGHT AFTER ROOT SECTOR) 
*         FOLLOWED BY EOR THEN ARRAY SEGMENT THEN A SECOND EOR. 
* 
*         THE CLOSEOUT ROUTINE WRITES THE TWO BINARY SEGMENTS, BUT
*         REQUIRES THAT THE CALLER FIRST FLUSH ALL TREE STRUCTURE 
*         SECTORS TO DISK OR AT LEAST TO THE CIRCULAR BUFFER.  THE
*         CALLER SHOULD USE THE CLEAN ROUTINE WITH PL=0 TO MEET THAT
*         REQUIREMENT.  THE CLOSEOUT ROUTINE DEPENDS ON THE RULE THAT 
*         THE ROOT SECTOR OF THE TREE STRUCTURE MUST BE THE LAST
*         (CHRONOLOGICALLY) TO BE FREED FOR UPDATE TO DISK, AND 
*         DEPENDS ON THE RULE THAT THE ROOT SECTOR IS AT DISK ADDRESS 
*         1 AND THEREFORE IS CONTIGUOUS WITH NO PREVIOUSLY RELEASED 
*         SECTORS.  THUS, CLOSEOUT EXPECTS THAT ALL SECTORS EXCEPT
*         THE ROOT HAVE BEEN ACTUALLY WRITTEN TO DISK, AND THE ROOT 
*         SECTOR IS EXPECTED TO BE SCHEDULED FOR UPDATE BUT STILL IS
*         PRESENT IN THE CIRCULAR BUFFER. 
* 
*         CLOSEOUT INTERCEPTS THE ISSUANCE OF CIO FUNCTION CODES FOR
*         THE UPDATE OF THE ROOT SECTOR, AND AFTER APPENDING THE
*         SCALAR DATA SEGMENT ONTO THE ROOT SECTOR IN THE CIRCULAR
*         BUFFER, CLOSEOUT CALLS PAUSEIO (AND THUS MOVEIO) SUCH THAT
*         ONE WRITE WITH END OF RECORD OCCURS.  THEN CLOSEOUT 
*         CONSTRUCTS A NEW CIRCULAR BUFFER IMAGE FOR THE ARRAY
*         SEGMENT, AND USES PAUSEIO A SECOND TIME TO WRITE ANOTHER
*         MULTIPLE SECTOR RECORD. 
* 
*         ENTRY  CLEAN HAS BEEN CALLED WITH PL=0. 
*                IOAPEND - WHETHER FILE PRESENTLY EMPTY.
*                LASTDA, MAXDA - LOGICAL AND PHYSICAL FILE SIZES. 
*                DATALEN, ARRAYLEN - LENGTHS OF BINARY SEGMENTS.
*                DATAPRUS, ARRAYPRUS - LENGTHS IN SECTORS.
* 
*         EXIT   FILE COMPLETELY DRAINED TO DISK, FET INACTIVE. 
*                LASTDA, MAXDA - ONE OF THESE TWO IS INCREMENTED. 
* 
*         CALLS  FLUSHCACHE, WTWBUF, PAUSEIO, INITFET.
* 
*         USES   P<FROM>, IOWRITE, IOWRTEOR, IOAPEND, IOACT.
# 
  FLUSHCACHE;        # PUT SECTOR 1 (ROOT PRU) INTO BUFFER   #
  IOWRTEOR=TRUE;     # WRITE ROOT PRU PLUS DATA SEGMENT      #
  P<FROM>=LOC(DATASTART); 
  WTWBUF(DATALEN);
  IF IOAPEND THEN MAXDA=MAXDA+DATAPRUS; 
  ELSE LASTDA=LASTDA+DATAPRUS;
  PAUSEIO;
  INITFET(DISK,DSKSIZ);        # NOW SET UP FOR ARRAYS       #
  IOACT=TRUE; 
  IOWRITE=TRUE; 
  IF FETCRI GR MAXDA THEN 
    BEGIN 
    IOAPEND=TRUE; 
    FETRR=LOC(DUMB);
    MAXDA=MAXDA+ARRAYPRUS;
    END 
  ELSE
    BEGIN 
    IOAPEND=FALSE;
    FETRR=FETCRI; 
    LASTDA=LASTDA+ARRAYPRUS;
    END 
  IOWRTEOR=TRUE;
  P<FROM>=LOC(ARRAYSTART);
  WTWBUF(ARRAYLEN); 
  PAUSEIO;
  IOEND                       # OF CLOSEOUT       # 
PAGE
PROC CLEAN; 
  IOBEGIN(CLEAN)
# 
**        CLEAN - DISCONNECT SECTOR FROM LOWEST TREE LEVEL. 
* 
*         CLEAN IS CALLED WHENEVER A ROUTINE WISHES TO DISCARD THE
*         SECTOR PRESENTLY ASSOCIATED WITH A TREE LEVEL.  THIS CAN BE 
*         DONE ONLY IN THE REVERSE ORDER OF THE TREE HIERARCHY, IE, 
*         THE BOTTOM LEVEL FIRST AND THE ROOT LEVEL LAST.  THE EFFECT 
*         OF CALLING CLEAN IS TO DISCONNECT THE SECTOR FROM THE TREE
*         STRUCTURE BY FREEING THE CACHE FRAME FROM OWNERSHIP, FIRST
*         DISCONNECTING ANY TREE LEVELS LOWER THAN THE CURRENT LEVEL. 
* 
*         IF THE TREE LEVEL WAS PREVIOUSLY FLAGGED AS ALTERED, THE
*         FREED CACHE FRAME IS SIMILARLY FLAGGED.  ALL HEADER WORD
*         DATA IS COPIED FROM THE TREE CONTROL VECTOR TO THE SECTOR 
*         IMAGE IN CACHE. 
* 
*         ENTRY  PL - LEVEL TO BE FREED.
*                PALVL - DEEPEST ACTIVE TREE LEVEL. 
*                PAHEAD[ALL] - HEADER WORDS IN TREE VECTOR. 
*                PACACHE[ALL] - SETUP.
*                CACHEOWNED[ALL] - SETUP. 
*                PADIRTY[ALL] - ALTERATION FLAG.
* 
*         EXIT   THIS LEVEL AND ALL DEEPER LEVELS FREED.
*                CACHEOWNED[ANY] - FORCED TRUE FOR THOSE FREED. 
*                CACHEDIRTY[ANY] - FORCED TRUE FOR THOSE FREED
*                    WITH PADIRTY TRUE. 
*                CACHEAGE[ANY] - INITIALIZED FOR THOSE FREED. 
* 
*         CALLS  DROPIT(INTERNAL), DISOWNCACHE. 
* 
*         USES   TPL. 
# 
  
  PROC DROPIT;
    BEGIN 
    IF CACHEOWNED[PACACHE[TPL]] THEN BFHEAD[PACACHE[TPL]]=PAHEAD[TPL];
    DISOWNCACHE(PACACHE[TPL]);
    END                       # OF DROPIT         # 
  
  # START OF MAIN CODE #
  
  IF PADIRTY[PL] THEN 
    BEGIN 
    FOR TPL = PALVL STEP -1 UNTIL PL DO 
      BEGIN 
      IF PADIRTY[TPL] THEN
        BEGIN 
        PADIRTY[TPL]=FALSE; 
        CACHEDIRTY[PACACHE[TPL]]=TRUE;
        DROPIT; 
        END 
      ELSE DROPIT;
      END 
    END 
  ELSE
    BEGIN 
    FOR TPL=PALVL STEP -1 UNTIL PL DO DROPIT; 
    END 
  
  IOEND 
  
  
PROC CLEANALL;
  IOBEGIN(CLEANALL) 
# 
**        CLEANALL - CLEAN ENTIRE TREE STRUCTURE AND DATA SEGEMENTS.
* 
*         CLEANALL IS USED TO INITIATE CLOSEOUT OF THE WORKFILE 
*         MANAGER.  THE ENTIRE TREE STRUCTURE IS CLEANED WITH ANY 
*         ALTERED SECTORS WRITTEN TO DISK, AND THE SCALAR AND ARRAY 
*         DATA SEGMENTS ARE UPDATED.
* 
*         ENTRY  CACHEOWNED[ALL] - SETUP. 
*                PACACHE[ALL] - SETUP.
*                PADIRTY[ALL] - SETUP.
*                PAHEAD[ALL] -SETUP.
*                PALVL - DEEPEST TREE LEVEL.
* 
*         EXIT   PL - ZERO. 
*                CACHEDIRTY[ALL] - FALSE. 
*                FET AND CIRCULAR BUFFER - COMPLETE/EMPTY.
*                PADIRTY[ALL] - FALSE.
* 
*         CALLS  CLEAN, CLOSEOUT, RECLAIMCACHE. 
# 
  PADIRTY[0]=TRUE;
  PL=0; 
  CLEAN;
  CLOSEOUT; 
  RECLAIMCACHE; 
  IOEND                       # OF CLEANALL       # 
PAGE
PROC BK;
  IOBEGIN(BK) 
# 
**        BK - BACK UP ONE LINE.
* 
*         BK IS ORGANIZED INTO THREE OPERATIONAL PHASES.  THE FIRST 
*         PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY
*         CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF 
*         NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM
*         THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY
*         WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE.
* 
*         THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE 
*         STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM)
*         LEVEL.  IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND 
*         PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK 
*         TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. 
*         THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP
*         AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA 
*         LEVEL.  HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP
*         FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE 
*         MUST DEEPEN THE TREE PATH.
* 
*         THE THIRD PHASE SCANS THE DATA SECTOR TO FIND THE RIGHT 
*         LINE IMAGE.  THE INTERNAL LINE IMAGE FORMAT USES THE SIGN 
*         BIT TO INDICATE WORDS WHICH ARE THE LAST WORDS OF LINES.
* 
*         ENTRY  CURRENT - CURRENT LINE.
*                PALVL - DEEPEST TREE LEVEL.
*                ALL PATH AND CACHE CONTROLS SETUP. 
*                P<MVLNBUF> - WHERE TO PUT LINE OF TEXT.
*                DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF 
*                    PREVIOUS CURRENT LINE. 
* 
*         EXIT   CURRENT - DECREMENTED. 
*                MVLNBUF - CONTAINS LINE OF TEXT. 
*                PALVL - NEW VALUE FOR DEEPEST TREE LEVEL.
*                ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS
*                    FOR DEEPER TREE LEVELS.
*                ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS.
*                FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL
*                    A PREFETCH OPERATION.
*                CURNW - SIZE OF NEW LINE.
*                DISP - OFFSET OF NEW LINE. 
* 
*         CALLS  TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH,
*                MOVELN.
* 
*         USES   TEMP(RESTORED), P<PRU>, PL.
# 
  IF CURRENT LQ 0 THEN TRAGIC(" BAK BEFORE START OF FILE.$"); 
  
  CURRENT = CURRENT - 1;
  
  FOR PL = PALVL WHILE PAFIRST[PL] GR CURRENT DO
    BEGIN 
    CLEAN;
    PL = PL - 1;
    END 
  
  P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
  DISP = PADISP[PL] - 1;
  CONTROL IFGQ PARANOIA,5;
    IF DISP LQ 0 THEN TRAGIC(" DIRECTORY BLOCK POINTER TOO SMALL.$"); 
  CONTROL FI; 
  PADISP[PL] = DISP;
  
  IF PADIR[PL] THEN 
    BEGIN 
    FOR PL = PL WHILE PADIR[PL] DO
      BEGIN 
      PL = PL + 1;
      PALVL = PL; 
  
      PUSHTEMP; 
      TEMP=DIRDA[DISP]; 
      READPA; 
      POPTEMP;
  
      PAFIRST[PL] = CURRENT - DIRNL[DISP] + 1;
      PALAST[PL] = CURRENT; 
  
      P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
      DISP = PAFINAL[PL]; 
      PADISP[PL] = DISP;
      END 
  
    PREFETCH(-1); 
    END 
  
  # NOTE THIS CODE REQUIRES INTERNAL CHARSET USAGE OF SIGN BIT #
  FOR DISP = DISP - 1 WHILE DISP NQ 0 AND B<0,1>DATWORD[DISP] EQ 0
    DO DISP = DISP - 1; 
  
  PADISP[PL] = DISP + 1;
  P<FROM> = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; 
  CURNW = MOVELN(FROM,MVLNBUF); 
  
  IOEND 
PAGE
PROC FW;
  IOBEGIN(FW) 
# 
**        FW - ADVANCE ONE LINE.
* 
*         FW IS ORGANIZED INTO THREE OPERATIONAL PHASES.  THE FIRST 
*         PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY
*         CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF 
*         NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM
*         THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY
*         WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE.
* 
*         THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE 
*         STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM)
*         LEVEL.  IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND 
*         PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK 
*         TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. 
*         THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP
*         AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA 
*         LEVEL.  HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP
*         FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE 
*         MUST DEEPEN THE TREE PATH.
* 
*         THE THIRD PHASE IDENTIFIES THE CORRECT LINE IMAGE WITHIN
*         THE DATA SECTOR.  THE MECHANISM USED DEPENDS ON WHETHER THE 
*         SECOND PHASE WAS ACTUALLY PERFORMED, SINCE FOR A NO-OP THE
*         DISP AND CURNW PARAMETERS FOR THE PREVIOUS CURRENT LINE 
*         PROVIDE ALL INFORMATION REQUIRED TO ADVANCE ONE LINE. 
* 
*         ENTRY  CURRENT - CURRENT LINE.
*                PALVL - DEEPEST TREE LEVEL.
*                ALL PATH AND CACHE CONTROLS SETUP. 
*                P<MVLNBUF> - WHERE TO PUT LINE OF TEXT.
*                DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF 
*                    PREVIOUS CURRENT LINE. 
* 
*         EXIT   CURRENT - INCREMENTED. 
*                MVLNBUF - CONTAINS LINE OF TEXT. 
*                PALVL - NEW VALUE FOR DEEPEST TREE LEVEL.
*                ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS
*                    FOR DEEPER TREE LEVELS.
*                ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS.
*                FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL
*                    A PREFETCH OPERATION.
*                CURNW - SIZE OF NEW LINE.
*                DISP - OFFSET OF NEW LINE. 
* 
*         CALLS  TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH,
*                MOVELN.
* 
*         USES   TEMP(RESTORED), P<PRU>, PL.
# 
  
  IF CURRENT GQ PALAST[0] THEN TRAGIC(" FWD BEYOND END OF FILE.$"); 
  
  CURRENT = CURRENT + 1;
  
  FOR PL = PALVL WHILE PALAST[PL] LS CURRENT DO 
    BEGIN 
    CLEAN;
    PL = PL - 1;
    END 
  
  P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
  DISP = PADISP[PL];
  
  IF PADIR[PL] THEN 
    BEGIN 
    DISP = DISP + 1;
    CONTROL IFGQ PARANOIA,5;
      IF DISP GR PAFINAL[PL] THEN 
        BEGIN 
        TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (1).$"); 
        END 
    CONTROL FI; 
    PADISP[PL] = DISP;
  
    FOR PL = PL WHILE PADIR[PL] DO
      BEGIN 
      PL = PL + 1;
      PALVL = PL; 
  
      PUSHTEMP; 
      TEMP=DIRDA[DISP]; 
      READPA; 
      POPTEMP;
  
      PAFIRST[PL] = CURRENT;
      PALAST[PL] = CURRENT + DIRNL[DISP] - 1; 
  
      P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
      DISP = 1; 
      PADISP[PL] = 1; 
      END 
  
    PREFETCH(+1); 
    END 
  
  ELSE PADISP[PL] = DISP + CURNW; 
  
  P<FROM> = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; 
  CURNW = MOVELN(FROM,MVLNBUF); 
  
  IOEND 
PAGE
PROC POSR;
  IOBEGIN(POSR) 
# 
**        POSR - POSITION TO A RANDOMLY SELECTED LINE.
* 
*         POSR IS ORGANIZED INTO THREE OPERATIONAL PHASES.  THE FIRST 
*         PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY
*         CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF 
*         NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM
*         THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY
*         WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE.
* 
*         THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE 
*         STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM)
*         LEVEL.  IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND 
*         PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK 
*         TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. 
*         THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP
*         AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA 
*         LEVEL.  HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP
*         FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE 
*         MUST DEEPEN THE TREE PATH.
* 
*         THE THIRD PHASE SCANS THE DATA SECTOR TO FIND THE RIGHT 
*         LINE IMAGE.  THE INTERNAL LINE IMAGE FORMAT USES THE SIGN 
*         BIT TO INDICATE WORDS WHICH ARE THE LAST WORDS OF LINES.
* 
*         ENTRY  NEWCURL - DESIRED LINE.
*                PALVL - DEEPEST TREE LEVEL.
*                ALL PATH AND CACHE CONTROLS SETUP. 
*                P<MVLNBUF> - WHERE TO PUT LINE OF TEXT.
*                DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF 
*                    PREVIOUS CURRENT LINE. 
* 
*         EXIT   CURRENT - MATCHES NEWCURL. 
*                MVLNBUF - CONTAINS LINE OF TEXT. 
*                PALVL - NEW VALUE FOR DEEPEST TREE LEVEL.
*                ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS
*                    FOR DEEPER TREE LEVELS.
*                ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS.
*                FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL
*                    A PREFETCH OPERATION.
*                CURNW - SIZE OF NEW LINE.
*                DISP - OFFSET OF NEW LINE. 
* 
*         CALLS  TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH,
*                MOVELN.
* 
*         USES   TEMP(RESTORED), P<PRU>, PL.
# 
  
  IF NEWCURL LS 0 OR NEWCURL GR PALAST[0] THEN
    BEGIN 
    TRAGIC(" LINE NOT FOUND IN FILE.$");
    END 
  
  PUSHTEMP; 
  
  CURRENT = NEWCURL;
  
  FOR PL=PALVL WHILE
    NOT(CURRENT GQ PAFIRST[PL] AND CURRENT LQ PALAST[PL]) DO
    BEGIN 
    CLEAN;
    PL = PL - 1;
    END 
  
  P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
  FOR PL = PL WHILE PADIR[PL] DO
    BEGIN 
    TEMP = PAFIRST[PL]; 
  
    FOR DISP = 1 WHILE CURRENT GQ TEMP+DIRNL[DISP] DO 
      BEGIN 
      TEMP = TEMP + DIRNL[DISP];
      DISP = DISP + 1;
      END 
  
    CONTROL IFGQ PARANOIA,5;
      IF DISP GR PAFINAL[PL] THEN 
        BEGIN 
        TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (2).$"); 
        END 
    CONTROL FI; 
    PADISP[PL] = DISP;
  
    PL = PL + 1;
    PALVL = PL; 
  
    PAFIRST[PL] = TEMP; 
    PALAST[PL] = TEMP + DIRNL[DISP] - 1;
  
    PUSHTEMP; 
    TEMP=DIRDA[DISP]; 
    READPA; 
    POPTEMP;
  
    P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
  
    END 
  
  TEMP = PAFIRST[PL]; 
  
  FOR DISP = 1 WHILE CURRENT NQ TEMP DO 
    BEGIN 
    # NOTE THIS CODE REQUIRES INTERNAL CHARSET USAGE OF SIGN BIT #
    IF B<0,1>DATWORD[DISP] NQ 0 THEN TEMP = TEMP + 1; 
    DISP = DISP + 1;
    END 
  
  CONTROL IFGQ PARANOIA,5;
    IF DISP GR PAFINAL[PL] THEN 
      BEGIN 
      TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (3).$"); 
      END 
  CONTROL FI; 
  
  PADISP[PL] = DISP;
  
  P<FROM> = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; 
  CURNW = MOVELN(FROM,MVLNBUF); 
  
  POPTEMP;
  
  IOEND 
PAGE
  
PROC FWD; 
  IOBEGIN(FWD)
# 
**        FWD - EXTERNAL INTERFACE FOR FORWARD MOVEMENT.
* 
*         FWD INTERFACES TO THE FW ROUTINE TO ADVANCE ONE LINE
*         FROM THE CURRENT POSITION.  FWD ALSO CONTROLS THE BASE
*         ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. 
* 
*         ENTRY  P<LINEBUF> - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE 
*                    POSITIONING FUNCTION.
*                CURRENT - CURRENT LINE.
* 
*         EXIT   CURRENT - INCREMENTED. 
*                LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO.
* 
*         CALLS  FW.
* 
*         USES   P<MVLNBUF>.
# 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  P<MVLNBUF>=LOC(LINEBUF);
  FW; 
  P<MVLNBUF>=0; 
  IOEND 
  
PROC BAK; 
  IOBEGIN(BAK)
# 
**        FWD - EXTERNAL INTERFACE FOR BACKWARD MOVEMENT. 
* 
*         BAK INTERFACES TO THE BK ROUTINE TO REGRESS ONE LINE
*         FROM THE CURRENT POSITION.  BAK ALSO CONTROLS THE BASE
*         ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. 
* 
*         ENTRY  P<LINEBUF> - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE 
*                    POSITIONING FUNCTION.
*                CURRENT - CURRENT LINE.
* 
*         EXIT   CURRENT - DECREMENTED. 
*                LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO.
* 
*         CALLS  BK.
* 
*         USES   P<MVLNBUF>.
# 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  P<MVLNBUF>=LOC(LINEBUF);
  BK; 
  P<MVLNBUF>=0; 
  IOEND 
  
PROC POS; 
  IOBEGIN(POS)
# 
**        POS - EXTERNAL INTERFACE FOR RANDOM MOVEMENT. 
* 
*         POS INTERFACES TO THE POSR ROUTINE TO GO TO ANY LINE
*         FROM THE CURRENT POSITION.  POS ALSO CONTROLS THE BASE
*         ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. 
* 
*         ENTRY  P<LINEBUF> - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE 
*                    POSITIONING FUNCTION.
*                NEWCURL - DESIRED LINE.
*                CURRENT - PREVIOUS CURRENT LINE. 
* 
*         EXIT   CURRENT - MATCHES NEWCURL. 
*                LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO.
* 
*         CALLS  POSR.
* 
*         USES   P<MVLNBUF>.
# 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  P<MVLNBUF>=LOC(LINEBUF);
  IF NEWCURL EQ CURRENT-1 THEN BK;
  ELSE IF NEWCURL EQ CURRENT+1 THEN FW; 
  ELSE IF NEWCURL NQ CURRENT THEN POSR; 
  ELSE                       # JUST READOUT SAME LINE      #
    BEGIN 
    P<FROM> = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; 
    DUMB = MOVELN(FROM,LINEBUF);
    END 
  P<MVLNBUF>=0; 
  IOEND                       # OF POS  # 
PAGE
  PROC SPLITTOP;
    IOBEGIN(SPLITTOP) 
# 
**        SPLITTOP - SPLIT ROOT SECTOR TO MAKE NEW LEVEL. 
* 
*         SPLITTOP IS USED WHEN THE ROOT SECTOR OVERFLOWS.  THE ROOT
*         SECTOR MUST ALWAYS EXIST AT DISK ADDRESS 1, THUS THE METHOD 
*         USED TO SPLIT IT IS DIFFERENT FROM THAT USED FOR ANY OTHER
*         SECTOR.  THE EXISTING CONTENTS OF THE ROOT SECTOR ARE 
*         PLACED IN TWO NEW SECTORS, AND THE ROOT SECTOR IS 
*         RECONSTRUCTED AS A DIRECTORY POINTING TO THESE TWO NEW
*         SECTORS.  THUS THE OVERALL TREE STRUCTURE IS DEEPENED BY
*         ONE LEVEL.
* 
*         HOWEVER, SPLITTOP DOES NOT PROVIDE ALL THIS ALGORITHM.
*         SPLITTOP DOES PRODUCE A NEW TREE LEVEL WITH A REBUILT ROOT, 
*         BUT STOPS SHORT OF FITTING THE OVERFLOWED TEXT INTO A PAIR
*         OF SECTORS.  SPLITTOP PLACES THE PRE-OVERFLOW SECTOR
*         CONTENT INTO A SINGLE SECTOR AT THE NEW INTERMEDIATE TREE 
*         LEVEL, AND ESTABLISHES THIS AS THE CURRENT TREE PATH LEVEL. 
*         THE CALLER IS THEN RESPONSIBLE TO USE ONE OF THE OTHER
*         SECTOR SPLITTING ALGORITHMS TO GET THE TEXT ACTUALLY SPLIT
*         INTO TWO SECTORS.  THESE OTHER ALGORITHMS WILL UPDATE THE 
*         NEW ROOT TO POINT TO THE OVERFLOW SECTOR. 
* 
*         ENTRY  PL - MUST EQUAL ZERO.
*                PADEPTH[0] - INDICATE HIGH WATER MARK OF TREE DEPTH. 
*                PALVL - CURRENT DEEPEST TREE LEVEL ON PATH.
*                ALL PATH CONTROLS - SETUP. 
*                CACHE - AT LEAST ONE FRAME MUST BE AVAILABLE TO
*                    BE CLAIMED.
* 
*         EXIT   PL - ONE.
*                PALVL - INCREMENTED. 
*                PADEPTH[0] - INCREMENETED. 
*                ALL PATH CONTROLS - SHUFFLED TO DEEPER LEVELS. 
*                CACHE - ONE FRAME ALLOCATED FOR NEW LEVEL 0. 
*                ROOT SECTOR CACHE FRAME - BUILT AS DIRECTORY WITH
*                    ONE ENTRY POINTING TO INTERMEDIATE SECTOR. 
*                PATH LEVEL 1 - BUILT AS INTERMEDIATE TREE LEVEL
*                    USING SAME CACHE FRAME AS FORMER ROOT LEVEL, 
*                    BUT ASSIGNED A NEWLY ALLOCATED DISK ADDRESS. 
*                    MARKED AS ALTERED SO NEW DISK ADDRESS CAN BE 
*                    EVENTUALLY WRITTEN TO DISK.
* 
*         CALLS  TRAGIC, ALLOC, ALLOCCACHE, MOVEWD. 
* 
*         USES   P<PRU>.
# 
    IF PADEPTH[0] GQ MAXDEPTH THEN TRAGIC(" FILE TOO LARGE.$"); 
    PADEPTH[0] = PADEPTH[0] + 1;
  
    FOR PL =  PALVL STEP -1 UNTIL 0 DO # SLIDE PATH UP     #
      BEGIN 
      PAFIRST[PL+1] = PAFIRST[PL];
      PALAST[PL+1] = PALAST[PL];
      PADISP[PL+1] = PADISP[PL];
      PAHEAD[PL+1] = PAHEAD[PL];
      PACACHE[PL+1] = PACACHE[PL];
      END 
  
    PL = 1;                  # STEP UP PL & PALVL          #
    PALVL = PALVL + 1;
  
    PATOP[1] = FALSE;        # FIX UP OLD TOP    #
    PADIRTY[1] = FALSE; 
    PADA[1] = 0;
    ALLOC;
    ALLOCCACHE; 
    PADIRTY[1] = TRUE;
    P<FROM> = LOC(BFPRU[PACACHE[0]]) + 1; 
    P<TOO> = LOC(BFPRU[PACACHE[1]]) + 1;
    MOVEWD(PAFINAL[0],FROM,TOO);
  
    PADATA[0] = FALSE;       # BUILD NEW TOP     #
    PADIR[0] = TRUE;
    PADIRTY[0] = TRUE;
    PAFINAL[0] = 1; 
    P<PRU> = LOC(BFPRU[PACACHE[0]]);
    DIRDA[1] = PADA[1]; 
    DIRNL[1] = PALAST[1] - PAFIRST[1] + 1;
  
    PADISP[0] = 1;
    IOEND 
PAGE
  PROC SPLITAPEND;
    IOBEGIN(SPLITAPEND) 
# 
**        SPLITAPEND - SPLIT SECTOR JUST AFTER CURRENT CONTENT. 
* 
*         SPLITAPEND IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER
*         THE CONDITION THAT THE OVERFLOW TEXT BELONGS JUST AFTER ALL 
*         OF THE TEXT ALREADY CONTAINED IN THE SECTOR.  THUS, THE 
*         SECTOR REMAINS JUST AS IT IS, AND A NEW SECTOR IS 
*         CONSTRUCTED WHICH CONTAINS PRECISELY THE OVERFLOW TEXT. 
*         SINCE NO EXISTING TEXT LEAVES THE SECTOR, THERE IS NO NEED
*         TO DISCARD IT AND BUILD TWO COMPLETELY NEW SECTORS.  THUS,
*         SPLITAPEND IS MORE EFFICIENT THAN SPLITBEFORE OR
*         SPLITAFTER. 
* 
*         THE NEWLY BUILT SECTOR BECOMES THE CURRENT SECTOR FOR THIS
*         LEVEL OF THE TREE.  THE ORIGINAL SECTOR IS RELEASED FROM
*         OWNERSHIP.
* 
*         SPLITAPEND REQUIRES THAT AT LEAST ONE CACHE FRAME IS
*         AVAILABLE FOR ALLOCATION. 
* 
*         SPLITAPEND FINISHES ITS PROCESSING BY SETTING UP PARAMETERS 
*         FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT
*         HIGHER LEVEL.  NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT 
*         THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT 
*         MORE SPLITTING NEEDS TO BE DONE.  SINCE EACH SPLIT ROUTINE
*         DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK
*         FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING 
*         UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. 
* 
*         ENTRY  P<NEW> - LOCATION OF OVERFLOW TEXT.
*                NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT.
*                PL - CURRENT LEVEL IN TREE PATH. 
*                ALL PATH CONTROLS SETUP. 
*                ALL CACHE CONTROLS SETUP.
* 
*         EXIT   PL - DECREMENTED.
*                SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN 
*                    OVERFLOW TEXT. 
*                PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. 
*                NWNEW, NEWBEFORE, NWAFTER - SET FOR ITERATION. 
*                NEWDA, NEWNL - SET FOR ITERATION.
*                DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION.
* 
*         CALLS  ALLOC, ALLOCCACHE, MOVEWD. 
* 
*         USES   P<TOO>.
# 
    CLEAN;                   # CLEAN OLD BLOCK   #
  
    ALLOC;                   # BUILD NEW BLOCK   #
    ALLOCCACHE; 
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + 1; 
    MOVEWD(NWNEW,NEW,TOO);
    PAFINAL[PL] = NWNEW;
    PADIRTY[PL] = TRUE; 
  
    PADISP[PL] = 1;          # FIX UP PATH       #
    PAFIRST[PL] = FIRST;
    PALAST[PL] = LAST;
  
    NEWDA[0] = PADA[PL];     # BUILD NEW DIR ENTRY         #
    NEWNL[0] = PALAST[PL] - PAFIRST[PL] + 1;
    NWNEW = 1;
  
    PL = PL - 1;             # SET UP NEXT PASS  #
    NWBEFORE = PADISP[PL];
    NWAFTER = PAFINAL[PL] - PADISP[PL]; 
    DISP = PADISP[PL] + 1;
    PADISP[PL] = DISP;
    IOEND 
PAGE
  PROC SPLITBEFORE; 
    IOBEGIN(SPLITBEFORE)
# 
**        SPLITBEFORE - SPLIT SECTOR JUST BEFORE INSERTION POINT. 
* 
*         SPLITBEFORE IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER 
*         THE CONDITION THAT THE OVERFLOW TEXT MUST BE INSERTED 
*         SOMEWHERE IN THE MIDDLE OF THE TEXT ALREADY CONTAINED IN
*         THE SECTOR, AND NEW TEXT WOULD NOT FIT WITHIN THE FIRST 
*         SECTOR OF THE NEW PAIR.  THUS, THE SECTOR IS SPLIT JUST IN
*         FRONT OF THE INSERTION POINT, AND THE SECOND SECTOR 
*         RESULTING FROM THE SPLIT WILL START WITH THE OVERFLOW TEXT
*         AND CONTINUE WITH THE REMAINING TEXT SPLIT OUT OF THE 
*         ORIGINAL SECTOR.
* 
*         SINCE SOME EXISTING TEXT MUST BE REMOVED FROM THE CURRENT 
*         SECTOR, IT IS NOT POSSIBLE TO KEEP USING THE SAME SECTOR, 
*         UNDER THE RULE THAT NOTHING CAN BE POINTED TO WITHOUT FIRST 
*         EXISTING ON DISK.  THEREFORE, TWO COMPLETELY NEW SECTORS
*         ARE CREATED AND THE ORIGINAL SECTOR BECOMES A FRAGMENT. 
*         SINCE THE INSERTED TEXT GOES INTO THE SECOND SECTOR OF THE
*         NEW PAIR, THAT IS THE SECTOR WHICH IS LEFT AS THE CURRENT 
*         SECTOR FOR THIS TREE PATH LEVEL.
* 
*         SPLITBEFORE REQUIRES THAT AT LEAST ONE CACHE FRAME IS 
*         AVAILABLE FOR ALLOCATION.  THE EXISTING CACHE FRAME FOR 
*         THIS TREE LEVEL IS RECYCLED TO PROVIDE THE SECOND SECTOR OF 
*         CAPACITY.  THE EXISTING CACHE FRAME IS FIRST SUBJECTED TO 
*         THE NORMAL "CLEAN" ROUTINE TO ASSURE THAT ANY PENDING 
*         CHANGES ARE FLUSHED TO DISK.
* 
*         SPLITBEFORE FINISHES ITS PROCESSING BY SETTING UP PARAMETERS
*         FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT
*         HIGHER LEVEL.  NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT 
*         THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT 
*         MORE SPLITTING NEEDS TO BE DONE.  SINCE EACH SPLIT ROUTINE
*         DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK
*         FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING 
*         UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. 
* 
*         ENTRY  P<NEW> - LOCATION OF OVERFLOW TEXT.
*                NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT.
*                PL - CURRENT LEVEL IN TREE PATH. 
*                ALL PATH CONTROLS SETUP. 
*                ALL CACHE CONTROLS SETUP.
* 
*         EXIT   PL - DECREMENTED.
*                SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN 
*                    OVERFLOW TEXT AND SPLIT-OFF ORGINAL TEXT.
*                PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. 
*                NWNEW, NEWBEFORE, NWAFTER - SET FOR ITERATION. 
*                NEWDA, NEWNL - SET FOR ITERATION.
*                DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION.
* 
*         CALLS  ALLOC, ALLOCCACHE, MOVEWD, LAST. 
* 
*         USES   P<TOO>.
# 
  
    P<FROM> = LOC(BFPRU[PACACHE[PL]]) + 1;      # SAVE OLD DATA     # 
    MOVEWD(PAFINAL[PL],FROM,OBF); 
  
    DEALLOC;                 # FREE UP OLD BLOCK #
  
    ALLOC;                   # BUILD OFF BLOCK   #
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + 1; 
    MOVEWD(NWBEFORE,OBF,TOO); 
    PAFINAL[PL] = NWBEFORE; 
  
    ODA = PADA[PL];          # REMEMBER THIS     #
    ONL = FIRST - PAFIRST[PL];
  
    PADIRTY[PL] = TRUE;      # GET RID OF OFF BLOCK        #
    CLEAN;
  
    ALLOC;                   # BUILD NEW BLOCK   #
    ALLOCCACHE; 
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + 1; 
    MOVEWD(NWNEW,NEW,TOO);
    P<FROM> = LOC(OBF) + DISP-1;
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + NWNEW + 1; 
    MOVEWD(NWAFTER,FROM,TOO); 
    PAFINAL[PL] = NWNEW + NWAFTER;
    PADIRTY[PL] = TRUE; 
  
    PADISP[PL] = PADISP[PL] - NWBEFORE;          # FIX UP PATH       #
    PAFIRST[PL] == FIRST; 
    PALAST[PL] = PALAST[PL] + NL; 
    LAST = PALAST[PL];
  
    NEWDA[0] = ODA;          # BUILD NEW DIR ENTRIES       #
    NEWNL[0] = ONL; 
    NEWDA[1] = PADA[PL];
    NEWNL[1] = PALAST[PL] - PAFIRST[PL] + 1;
    NWNEW = 2;
  
    PL = PL - 1;             # SET UP NEXT PASS  #
    NWBEFORE = PADISP[PL] - 1;
    NWAFTER = PAFINAL[PL] - PADISP[PL]; 
    DISP = PADISP[PL] + 1;
    PADISP[PL] = DISP;
    IOEND 
PAGE
  PROC SPLITAFTER;
    IOBEGIN(SPLITAFTER) 
# 
**        SPLITAFTER - SPLIT SECTOR JUST AFTER INSERTION POINT. 
* 
*         SPLITAFTER IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER
*         THE CONDITION THAT THE OVERFLOW TEXT MUST BE INSERTED 
*         SOMEWHERE IN THE MIDDLE OF THE TEXT ALREADY CONTAINED IN
*         THE SECTOR, AND NEW TEXT WOULD FIT WITHIN THE FIRST 
*         SECTOR OF THE NEW PAIR.  THUS, THE SECTOR IS SPLIT JUST 
*         BEYOND THE INSERTED TEXT, AND THE SECOND SECTOR 
*         RESULTING FROM THE SPLIT WILL START WITH THE REMAINING
*         TEXT SPLIT OUT OF THE ORGINAL SECTOR. 
* 
*         SINCE SOME EXISTING TEXT MUST BE REMOVED FROM THE CURRENT 
*         SECTOR, IT IS NOT POSSIBLE TO KEEP USING THE SAME SECTOR, 
*         UNDER THE RULE THAT NOTHING CAN BE POINTED TO WITHOUT FIRST 
*         EXISTING ON DISK.  THEREFORE, TWO COMPLETELY NEW SECTORS
*         ARE CREATED AND THE ORIGINAL SECTOR BECOMES A FRAGMENT. 
*         SINCE THE INSERTED TEXT GOES INTO THE FIRST SECTOR OF THE 
*         NEW PAIR, THAT IS THE SECTOR WHICH IS LEFT AS THE CURRENT 
*         SECTOR FOR THIS TREE PATH LEVEL.
* 
*         SPLITAFTER REQUIRES THAT AT LEAST ONE CACHE FRAME IS
*         AVAILABLE FOR ALLOCATION.  THE EXISTING CACHE FRAME FOR 
*         THIS TREE LEVEL IS RECYCLED TO PROVIDE THE SECOND SECTOR OF 
*         CAPACITY.  THE EXISTING CACHE FRAME IS FIRST SUBJECTED TO 
*         THE NORMAL "CLEAN" ROUTINE TO ASSURE THAT ANY PENDING 
*         CHANGES ARE FLUSHED TO DISK.
* 
*         SPLITAFTER FINISHES ITS PROCESSING BY SETTING UP PARAMETERS 
*         FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT
*         HIGHER LEVEL.  NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT 
*         THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT 
*         MORE SPLITTING NEEDS TO BE DONE.  SINCE EACH SPLIT ROUTINE
*         DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK
*         FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING 
*         UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. 
* 
*         ENTRY  P<NEW> - LOCATION OF OVERFLOW TEXT.
*                NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT.
*                PL - CURRENT LEVEL IN TREE PATH. 
*                ALL PATH CONTROLS SETUP. 
*                ALL CACHE CONTROLS SETUP.
* 
*         EXIT   PL - DECREMENTED.
*                SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN 
*                    OVERFLOW TEXT AND SPLIT-OFF ORGINAL TEXT.
*                PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. 
*                NWNEW, NEWAFTER, NWAFTER - SET FOR ITERATION.
*                NEWDA, NEWNL - SET FOR ITERATION.
*                DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION.
* 
*         CALLS  ALLOC, ALLOCCACHE, MOVEWD, CLEAN, DEALLOC. 
* 
*         USES   P<TOO>, FIRST, LAST. 
# 
  
    P<FROM> = LOC(BFPRU[PACACHE[PL]]) + 1;      # SAVE OLD DATA     # 
    MOVEWD(PAFINAL[PL],FROM,OBF); 
  
    DEALLOC;                 # FREE UP OLD BLOCK #
  
    ALLOC;                   # BUILD OFF BLOCK   #
    P<FROM> = LOC(OBF) + DISP-1;
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + 1; 
    MOVEWD(NWAFTER,FROM,TOO); 
    PAFINAL[PL] = NWAFTER;
  
    ODA = PADA[PL];          # REMEMBER THIS     #
    ONL = PALAST[PL] + NL - LAST; 
  
    PADIRTY[PL] = TRUE;      # GET RID OF OFF BLOCK        #
    CLEAN;
  
    ALLOC;                   # BUILD NEW BLOCK   #
    ALLOCCACHE; 
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + 1; 
    MOVEWD(NWBEFORE,OBF,TOO); 
    P<TOO> = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE + 1;
    MOVEWD(NWNEW,NEW,TOO);
    PAFINAL[PL] = NWBEFORE + NWNEW; 
    PADIRTY[PL] = TRUE; 
  
    FIRST = PAFIRST[PL];     # FIX UP PATH       #
    LAST == PALAST[PL]; 
    LAST = LAST + NL; 
  
    NEWDA[0] = PADA[PL];     # BUILD NEW DIR ENTRIES       #
    NEWNL[0] = PALAST[PL] - PAFIRST[PL] + 1;
    NEWDA[1] = ODA; 
    NEWNL[1] = ONL; 
    NWNEW = 2;
  
    PL = PL - 1;             # SET UP NEXT PASS  #
    NWBEFORE = PADISP[PL] - 1;
    NWAFTER = PAFINAL[PL] - PADISP[PL]; 
    DISP = PADISP[PL] + 1;
    IOEND 
PAGE
PROC CHANGE;                 # CHANGE TREE       #
  IOBEGIN(CHANGE) 
# 
**        CHANGE - APPLY CHANGES TO TREE STRUCTURE. 
* 
*         CHANGE IS THE PRINCIPAL ALGORITHM USED TO MANIPULATE THE
*         TREE STRUCTURE.  CHANGE IS DESIGNED TO BE CALLED BY THE 
*         INS, REP, AND DEL ENTRY POINTS.  THE ALGORITHM WORKS IN 
*         THREE PHASES.  THE FIRST PHASE TAKES CARE OF CASES WHERE AN 
*         INSERTION OF A LINE OR THE REPLACEMENT OF A LINE WITH AN
*         ENLARGED VERSION WILL CAUSE THE CURRENT DATA SECTOR (THE
*         BOTTOM LEVEL OF THE TREE STRUCTURE) TO OUTGROW THE SECTOR 
*         CAPACITY AND CAUSE SPLITTING INTO TWO SECTORS.  THIS CAUSES 
*         ADDITIONAL DIRECTORY ENTRIES TO BE REQUIRED IN THE HIGHER 
*         TREE LEVELS, WHICH CAN IN TURN CAUSE ONE OR MORE DIRECTORY
*         LEVELS TO BE SPLIT INTO MULTIPLE SECTORS.  IN THE EXTREME 
*         CASE, THE ROOT LEVEL OF THE TREE MAY OVERFLOW, IN WHICH 
*         CASE A NEW INTERMEDIATE LEVEL OF THE TREE MUST BE CREATED,
*         WITH THE ROOT SECTOR CONVERTED INTO A DIRECTORY FOR THE 
*         INTERMEDIATE LEVEL. 
* 
*         AFTER THE FIRST PHASE HAS EXHAUSTED ITS ITERATION, THE
*         SECOND PHASE TAKES OVER, IDENTIFYING ANY TREE LEVEL 
*         STARTING WITH THE DATA LEVEL WHICH HAS BECOME EMPTY AS THE
*         RESULT OF DELETIONS.  THE SECOND PHASE IS THUS MUTUALLY 
*         EXCLUSIVE OF THE FIRST PHASE. 
* 
*         THE THIRD PHASE IS A CLEANUP OPERATION.  WHILE EACH OF THE
*         FIRST TWO PHASES MAY BE A NO-OP, (AND AT LEAST ONE OF THE 
*         FIRST TWO PHASES WILL INDEED BE A NO-OP), THE THIRD PHASE 
*         ALWAYS IS IN EFFECT.  THE CLEANUP OPERATION CONSISTS OF THE 
*         APPLICATION OF LEFT-OVER CHANGES.  LEFTOVER CHANGES MAY 
*         CONSTITUTE THE ENTIRE CHANGE IF NEITHER PRELIMINARY PHASE 
*         WAS NEEDED, OR IF ONE OF THE FIRST TWO PHASES WAS USED, 
*         THEN IT WILL HAVE LEFT RESIDUAL CHANGES TO BE APPLIED ONE 
*         LEVEL CLOSER TO THE ROOT THAN THE LAST LEVEL AFFECTED BY
*         THE SPLITTING OR COLLAPSING.
* 
*         ACTUAL APPLICATION OF CHANGES, WHETHER PERFORMED BY THE 
*         THIRD PHASE OF THE CHANGE ROUTINE, OR WITHIN ONE OF THE 
*         SPLIT ROUTINES SELECTED BY THE FIRST PHASE OF THE CHANGE
*         ROUTINE, SIMPLY CONSIST OF OPENING UP A GAP AT THE RIGHT
*         MEMORY LOCATION, OF ZERO, POSITIVE, OR NEGATIVE LENGTH, AND 
*         MOVING IN THE TEXT OF THE CHANGE, WHICH CAN CONSIST OF ZERO 
*         OR MORE WORDS.
* 
*         THE CLEANUP PHASE ALSO INCREMENTS OR DECREMENTS THE COUNT 
*         OF LINES BRACKETED BY EACH LEVEL OF THE TREE. 
* 
*         ENTRY  PALVL - DEEPEST LEVEL IN TREE STRUCTURE. 
*                PL - CURRENT TREE LEVEL MUST EQUAL PALVL.
*                NL - CHANGE IN NUMBER OF LINES IN WORKFILE.
*                NWBEFORE - NUMBER OF WORDS BEFORE POINT OF CHANGE. 
*                NWAFTER - NUMBER OF WORDS AFTER POINT OF CHANGE. 
*                NWNEW - NUMBER OF WORDS IN CHANGED TEXT. 
*                DISP - POINTS JUST AFTER CURRENT LINE. 
*                LINEBUF - CONTAINS TEXT OF CHANGE. 
*                ALL PATH CONTROLS - SETUP. 
*                ALL CACHE CONTROLS - SETUP.
* 
*         EXIT   PL - ZERO. 
*                PALVL - INCREMENTED/DECREMENTED IF TREE DEPTH CHANGED. 
*                DISP, CURNW - BRACKET NEW CURRENT LINE.
*                ALL PATH, CACHE CONTROLS - UPDATED.
*                FET AND CIRCULAR BUFFER USED IF NEEDED.
* 
*         CALLS  COUNTCACHE, FLUSHCACHE, SPLITTOP, SPLITAPEND,
*                SPLITBEFORE, SPLITAFTER, RECLAIMCACHE, DEALLOC, CLEAN, 
*                MOVEWD.
* 
*         USES   P<NEW>, P<FROM>, P<TOO>, FIRST, LAST, NWNEW, 
*                NWBEFORE, NWAFTER, NL. 
# 
  P<NEW> = LOC(LINEBUF);         # SET THINGS UP     #
  FIRST = CURRENT + NL; 
  LAST = FIRST; 
  CURNW = NWNEW;
  
  FOR PL = PL WHILE NWBEFORE+NWNEW+NWAFTER GR 63 DO 
    BEGIN                     # DO ALL SPLITS     # 
    IF COUNTCACHE LQ 2 THEN FLUSHCACHE; 
    IF PL EQ 0 THEN SPLITTOP; 
    IF NWBEFORE EQ PAFINAL[PL] THEN SPLITAPEND; 
    ELSE IF NWBEFORE+NWNEW LQ 63 THEN SPLITAFTER; 
    ELSE SPLITBEFORE; 
    RECLAIMCACHE; 
    P<NEW> = LOC(NDIR); 
    END 
  
  FOR PL = PL WHILE NWBEFORE+NWNEW+NWAFTER EQ 0 DO
    BEGIN                     # DO ALL COLLAPSING # 
    DEALLOC;
    CLEAN;                   # TO FREE UP CACHE FRAME # 
    PALVL = PALVL - 1;
    PL = PL - 1;
    NWBEFORE = PADISP[PL] - 1;
    NWAFTER = PAFINAL[PL] - PADISP[PL]; 
    DISP = PADISP[PL] + 1;
    END 
  
  P<FROM> = LOC(BFPRU[PACACHE[PL]]) + DISP;        # SLIDE AFTER       #
  P<TOO> = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE +NWNEW + 1; 
  MOVEWD(NWAFTER,FROM,TOO); 
  
  P<TOO> = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE + 1; # MOVE IN NEW  # 
  MOVEWD(NWNEW,NEW,TOO);
  
  PAFINAL[PL] = NWBEFORE + NWNEW + NWAFTER; 
  PADIRTY[PL] = TRUE; 
  PALAST[PL] = PALAST[PL] + NL;        # FIX UP PATH       #
  
  FOR PL = PL-1 STEP -1 UNTIL 0 DO     # UPDATE HIGHER DIRS       # 
    BEGIN 
    P<PRU> = LOC(BFPRU[PACACHE[PL]]); 
    DIRNL[PADISP[PL]] = DIRNL[PADISP[PL]] + NL; 
    PADIRTY[PL] = TRUE; 
    PALAST[PL] = PALAST[PL] + NL; 
    END 
  
  CURRENT = CURRENT + NL;          # UPDATE CURRENT       # 
  
  IOEND 
PAGE
  
PROC INS; 
  IOBEGIN(INS)
# 
**        INS - INTERFACE FOR INSERTION OF LINES. 
* 
*         ENTRY  CURRENT - CURRENT LINE.
*                LINEBUF - LINE IMAGE.
*                ALL PATH AND CACHE CONTROLS SETUP. 
* 
*         EXIT   CURRENT - INCREMENETED.
*                ALL PATH AND CACHE CONTROL UPDATED.
* 
*         CALLS  LINESZ, CHANGE, WIOSTAT. 
* 
*         USES   PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP.
# 
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  PL = PALVL;                # STANDARD INITCHANGE         #
  DISP = PADISP[PL];
  
  NL = 1; 
  NWBEFORE = DISP + CURNW - 1;
  NWNEW = LINESZ(LINEBUF);
  NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; 
  DISP = DISP + CURNW;
  
  PADISP[PL] = DISP;
  
  CHANGE; 
  IOEND 
  
PROC REP; 
  IOBEGIN(REP)
# 
**        REP - INTERFACE FOR REPLACEMENT OF LINES. 
* 
*         ENTRY  CURRENT - CURRENT LINE.
*                LINEBUF - LINE IMAGE.
*                ALL PATH AND CACHE CONTROLS SETUP. 
* 
*         EXIT   CURRENT - UNCHANGED. 
*                ALL PATH AND CACHE CONTROL UPDATED.
* 
*         CALLS  LINESZ, CHANGE, WIOSTAT. 
* 
*         USES   PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP.
# 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  PL = PALVL;                # STANDARD INITCHANGE         #
  DISP = PADISP[PL];
  
  NL = 0; 
  NWBEFORE = DISP - 1;
  NWNEW = LINESZ(LINEBUF);
  NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; 
  DISP = DISP + CURNW;
  
  CHANGE; 
  IOEND 
  
PROC DEL; 
  IOBEGIN(DEL)
# 
**        DEL - INTERFACE FOR DELETION OF LINES.
* 
*         ENTRY  CURRENT - CURRENT LINE.
*                ALL PATH AND CACHE CONTROLS SETUP. 
*                DELETCTL - POST-POSITIONING INCREMENT. 
* 
*         EXIT   CURRENT - INCREMENTED IF DELETCTL WAS ONE. 
*                LINEBUF - NEW CURRENT LINE IS READ UP. 
*                ALL PATH AND CACHE CONTROL UPDATED.
*                DELETCTL - FORCED ZERO IF DELETED LAST LINE. 
* 
*         CALLS  CHANGE, WIOSTAT, POSR. 
* 
*         USES   PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP,
*                P<MVLNBUF>, NEWCURL. 
# 
  
  CONTROL IFEQ MULTI,1; 
    WIOSTAT(0);               # ACCUMULATE LINE ACCESES # 
  CONTROL FI; 
  
  PL = PALVL;                # STANDARD INITCHANGE         #
  DISP = PADISP[PL];
  
  NL = -1;
  NWBEFORE = DISP - 1;
  NWNEW = 0;
  NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; 
  DISP = DISP + CURNW;
  
  CHANGE; 
  
  IF PALAST[0] LQ CURRENT THEN DELETCTL=0;
  CURRENT=CURRENT+DELETCTL; 
  NEWCURL=CURRENT;
  P<MVLNBUF>=LOC(LINEBUF);
  POSR; 
  P<MVLNBUF>=0; 
  
  IOEND 
PAGE
PROC INIT;
  BEGIN 
# 
**        INIT - INITIALIZE MEMORY CELLS FOR WORKIO.
* 
*         ENTRY  NO CONDITIONS. 
* 
*         EXIT   P<LINEBUF> -POINTS TO LIN ARRAY. 
*                ALL ELEMENTS OF PATH ARRAY - NOMINAL.
*                ALL CACHE CONTROLS - NOMINAL.
*                READ LIST (RDLIST, RDIN, RDOUT) - EMPTY. 
*                FLAGS (IOACT, IOREAD, IOWRITE) - FALSE.
*                ARRAYLEN, DATALEN, ARRAYPRUS, DATAPRUS - INITIALIZED.
*                SEGEMENTVER - ZEROED OUT.
# 
  P<LINEBUF>=LOC(LIN);
  
  FOR PL = 1 STEP 1 UNTIL MAXDEPTH DO  # INIT PATH         #
    BEGIN 
    PAFIRST[PL] = 0;
    PALAST[PL] = 0; 
    PADISP[PL] = 0; 
    PAHEAD[PL] = 0; 
    END 
  
  FOR PL = 0 STEP 1 UNTIL MAXCACHE DO  # INIT CACHE CTL    #
    BEGIN 
    CACHEOWNED[PL]=FALSE; CACHEDIRTY[PL]=FALSE; 
    CACHEAGE[PL]=-1;
    BFHEAD[PL]=0; 
    END 
  
  FOR RDIN = 0 STEP 1 UNTIL RDLIM DO RDLIST[RDIN] = 0;
  
  IOACT = FALSE;             # INIT IO #
  IOAPEND = FALSE;
  IOWRITE = FALSE;
  IOREAD = FALSE; 
  
  CURNW = 0;
  
  SEGMENTVER=0; 
  ARRAYLEN=LOC(ARRAYEND)-LOC(ARRAYSTART); 
  ARRAYPRUS=(ARRAYLEN+O"77")/O"100";
  DATALEN=LOC(DATAEND)-LOC(DATASTART);
  DATAPRUS=(DATALEN+O"77")/O"100";
  
  END 
PAGE
PROC RESUMIO; 
  IOBEGIN(RESUMIO)
# 
**        RESUMIO - RESUME EDITOR STATUS FROM LEFTOVER WORKFILE.
* 
*         RESUMIO ATTEMPTS TO VALIDATE A FILE AS CONTAINING THE TEXT
*         AND STATUS LEFT BY A PREVIOUS EDIT SESSION, OR BY A 
*         SINGLE/MULTI USER TRANSITION.  SINCE THE SCALAR AND ARRAY 
*         DATA SEGMENTS CONTAIN NEARLY ALL EDITOR DATA, SUCCESSFUL
*         EXECUTION OF RESUMIO CHANGES EVERYTHING.
* 
*         THE FOLLOWING CONDITIONS ARE REQUIRED TO VALIDATE AN
*         APPARENT WORKFILE AS A LEGITIMATE BASIS FOR RESUMPTION -- 
* 
*                1. THE FIRST WORD MUST BE FORMATTED AS A ROOT SECTOR 
*                HEADER WORD.  THE DISK ADDRESS FIELD MUST BE ONE AND 
*                THE TOP FLAG MUST BE ON AND THE DIRECTORY AND DATA 
*                FLAGS MUST BE UNEQUAL. 
* 
*                2. THE FIRST RECORD OF THE FILE MUST BE OF LENGTH
*                64 WORDS LONGER THAN THE SCALAR DATA SEGMENT, AND
*                THE SECOND RECORD OF THE FILE MUST EXACTLY MATCH 
*                THE LENGTH OF THE ARRAY SEGMENT. 
* 
*                3. THE SEGMENTLEN1 AND SEGMENTLEN2 FIELDS OF THE SCALAR
*                SEGMENT MUST MATCH THE ABOVE LENGTHS, AND THE
*                SEGMENTVER FIELD MUST MATCH THE VERSION NUMBER 
*                COMPILED INTO THIS PROGRAM.
* 
*         AFTER READING UP ALL EDITOR DATA, THE FOLLOWING WORKIO
*         CONTROLS ARE RE-INITIALIZED - THE PATH VECTOR IS REFRESHED
*         TO MATCH THE CURRENT LINE, THE SCHEDULING FLAGS (IOACT, 
*         IOREAD, ETC.) ARE CLEARED, MAXDA AND DANEXT ARE BASED ON
*         FILE SIZE, AND SEGMENTVER IS CLEARED.  THE WORKFILE IS THEN 
*         IMMEDIATELY CHECKPOINTED SO THAT IT CANNOT BE RESUMED 
*         AGAIN, THUS INTERLOCKING IT.
* 
*         ENTRY  NO CONDITIONS. 
* 
*         EXIT   EVERYTHING IN THE WHOLE EDITOR IS CHANGED. 
*                IORESUMED - SUCCESS/FAILURE INDICATOR. 
* 
*         CALLS  INIT, INITFET, BUFUSAGE, ALLOCCACHE, RDWBUF, READ, 
*                WAIT, TRAGIC, SKIPEI, POSR, CLEANALL.
# 
  INIT; 
  IORESUMED=FALSE;
  INITFET(DISK,DSKSIZ); 
  
  REWIND(FET,0);             # TRY TO READ PRU 1 #
  WAIT; 
  READ(FET,0);
  WAIT; 
  
  IF BUFUSAGE GR O"100" THEN
    BEGIN 
    PL=0; 
    PADA[0]=1;
    ALLOCCACHE; 
    P<TOO>=LOC(BFPRU[PACACHE[0]]);
    RDWBUF(O"100"); 
    PAHEAD[0] = BFHEAD[PACACHE[0]]; 
    END 
  
  IF PADA[0] EQ 1 AND PATOP[0]
    AND BOOLDIFF(PADIR[0],PADATA[0]) THEN 
    BEGIN 
  
    # READ FIRST FOUR WORDS TO VERIFY SEGMENT DESCRIPTORS # 
    P<TOO>=LOC(DATASTART);
    NWNEW=BUFUSAGE; 
    NWNEW=MIN(NWNEW,4); 
    RDWBUF(NWNEW);
    IORESUMED=FALSE;
    IF SEGMENTVER NQ IOVERSION OR SEGMENTLEN1 NQ DATALEN
      OR SEGMENTLEN2 NQ ARRAYLEN THEN IORET 
  
    # SEGMENT DESCRIPTORS VALID, READ REST OF SCALAR SEGMENT #
    P<TOO>=LOC(DATASTART)+4;
    NWNEW=BUFUSAGE; 
    NWNEW=MIN(DATALEN-4,NWNEW); 
    RDWBUF(NWNEW);
    READ(FET,0);
    WAIT; 
  
    # READ ARRAY SEGMENT #
    P<TOO>=LOC(ARRAYSTART); 
    NWNEW=BUFUSAGE; 
    NWNEW=MIN(ARRAYLEN,NWNEW);
    RDWBUF(NWNEW);
  
    IF ABORTED THEN TRAGIC(ERRSTRING);
  
    SKIPEI(FET,0);
    WAIT; 
    MAXDA=FETCRI-1; 
    DANEXT=FETCRI;
  
    PADISP[0] = 1;           # SET UP PATH[0]    #
    PAFIRST[0] = 0; 
    PALAST[0] = -1; 
    P<PRU> = LOC(BFPRU[PACACHE[0]]);
    IF PADIR[0] THEN
      BEGIN 
      FOR DISP = 1 STEP 1 UNTIL PAFINAL[0]
        DO PALAST[0] = PALAST[0] + DIRNL[DISP]; 
      END 
    ELSE                     # NOTE INTERNAL CHARSET SIGN BIT USAGE  #
      BEGIN 
      FOR DISP = 1 STEP 1 UNTIL PAFINAL[0]
        DO IF B<0,1>DATWORD[DISP] NQ 0 THEN PALAST[0] = PALAST[0] + 1;
      END 
  
    IORESUMED=TRUE;          # RESET FLAGS READ WITH DATA  #
    IOACT=FALSE;
    IOREAD=FALSE; 
    IOWRITE=FALSE;
    IOAPEND=FALSE;
  
    PALVL = 0;               # BUILD REST OF PATH          #
    NEWCURL=0;
    P<MVLNBUF>=LOC(LINEBUF);
    POSR; 
    P<MVLNBUF>=0; 
  
    SEGMENTVER=0;            # INTERLOCK FILE ONWESHIP     #
    CLEANALL; 
  
    END 
  
  IOEND 
PAGE
  
CONTROL IFEQ SINGLE,1;
  
PROC INITIO;
  IOBEGIN(INITIO) 
# 
**        INITIO - FORMAT A NEW WORKFILE. 
* 
*         INITIO IS USED TO BUILD A WORKFILE FROM SCRATCH OR TO 
*         DISCARD OLD WORKFILE CONTENT AND REFRESH IT.
* 
*         ENTRY  NO CONDITIONS. 
* 
*         EXIT   FILE INITIALIZED.
*                ALL PATH CONTROLS NOMINAL. 
*                ALL CACHE CONTROL NOMINAL. 
*                MAXDA - ZERO 
*                DANEXT - FIRST ALLOCATABLE DISK ADDRESS RIGHT AFTER
*                    ROOT SECTOR, AND SCALAR AND ARRAY DATA SEGMENTS. 
*                IORESUMED - TRUE TO SHOW WORKIO ACTIVE.
*                SEGMENTVER - ZERO IN MEMORY AND ON DISK TO INTERLOCK.
* 
*         CALLS  INITFET, EVICT, WAIT, INIT, ALLOCCACHE, CLEANALL.
# 
  INITFET(DISK,DSKSIZ); 
  EVICT(FET,0); 
  WAIT; 
  
  INIT; 
  MAXDA = 0;                 # SET MAXDA AND DANEXT        #
  DANEXT = 2+DATAPRUS+ARRAYPRUS;
  
  PAHEAD[0] = 0;             # BUILD TOP BLOCK   #
  PADATA[0] = TRUE; 
  PATOP[0] = TRUE;
  PADIRTY[0] = TRUE;
  PADA[0] = 1;
  PAFINAL[0] = 1; 
  PL=0; 
  ALLOCCACHE; 
  P<PRU> = LOC(BFPRU[PACACHE[0]]);
  DATWORD[1] = NULLIN;
  CURNW = 1;
  
  PADEPTH[0] = 0; 
  PADISP[0] = 1;             # SET UP PATH       #
  PAFIRST[0] = 0; 
  PALAST[0] = 0;
  PALVL = 0;
  CURRENT = 0;
  IORESUMED=TRUE;            # SHOW WORKIO ALIVE #
  
  SEGMENTVER = 0;            # INTERLOCK FILE OWNERSHIP    #
  CLEANALL; 
  
  IOEND 
  
CONTROL FI; 
PAGE
PROC CHECKIO; 
  IOBEGIN(CHECKIO)
# 
**        CHECKIO - CHECKPOINT WORKFILE.
* 
*         CHECKIO IS USED TO ASSURE THAT ALL MEMORY DATA IS FULLY 
*         WRITTEN TO DISK.  THIS CAN BE USED TO PREPARE FOR END OF
*         JOB STEP OR FOR TRANSITION BETWEEN SINGLE AND MULTIPLE USER 
*         VERSIONS OF THE EDITOR.  BY CHECKPOINTING THE WORKFILE, IT
*         BECOMES POSSIBLE TO EXECUTE THE RESUMIO ROUTINE AND FULLY 
*         RESTORE THE EDITOR STATUS.
* 
*         ENTRY  IORESUMED - SHOWS WHETHER WORKFILE MANAGER EVER ACTIVE.
*                CURRENT - CURRENT LINE ORDINAL.
* 
*         EXIT   SEGMENTVER, SEGMENTLEN1, SEGMENTLEN2 - SETUP.
*                CACHE FLUSHED, FET AND CIRCULAR BUFFER COMPLETE. 
* 
*         CALLS  CLEANALL.
# 
  IF NOT IORESUMED THEN IORET  # WORKIO NEVER ALIVE          #
  
  SAVECURL=CURRENT; 
  SEGMENTVER = IOVERSION;    # SHOW VALID WORKFILE FORMAT  #
  SEGMENTLEN1 = DATALEN;
  SEGMENTLEN2 = ARRAYLEN; 
  CLEANALL;                  # WRITE OUT WHOLE PATH        #
  
  IOEND 
  
  
  END TERM
