;       SEQUENTIAL FILE I/O LIBRARY

;       VRS 2.2    Jack Riley, Boulder Colorado(RCPM phone: (303)499-9169)
;
;       This is a highly modified version of the original by unknown author
;       believed to be Ward Christensen.
;       It has been expanded to include the following new features:
;       1) An APPEND mode to the FILE macro to allow the opening of files
;          with automatic positioning to the EOF. Both GET and PUT macros
;          are expanded to allow full random access to the file. Random
;          access reads and writes are used instead of sequential(and also
;          in other modes of use of the FILE macro so 1.4 is now incompatible).
;       2) PUBLIC and NONLOC options have been added to the FILE macro
;          to allow access to files not in the current user area or on the
;          current disk drive. The GET and PUT macros also handle the switching
;          needed to provide for multiple opens in multiple areas. The way
;          they work is to momentarily switch the user area to the one needed
;          for the file undergoing an IO operation. A return is made to the
;          'home' user area to allow for 'local' file accesses or switches
;          to other areas to access other files. This is not completely
;          satisfactory and one could wish for a more elegant method which
;          should have been available under CPM. Also an additional byte
;          has been added to the FCB generated by FILLFCB to contain the
;          user area. The NONLOC option prevents an otherwise automatic
;          sequence to look first in the current user area and on the current
;          disk for the file, then switch the user area, then the disk to
;          the default locations. When PUBLIC is included in an invocation
;          of FILE, then code accessing default and current values is made.
;          The allocations for these variables is shown below.
;               DEFAULT$USER:
;                       DB      0       ; or other user area
;               DEFAULT$DISK:
;                       DB      'x'-'A' ; where x is the default
;               CUR$USER:
;                       DB      0FFH    ; necessary initial value
;               CUR$DISK:
;                       DB      0FFH    ;  "           "
;
;          The intention was to allow the default values to be modified
;          at run time(one of the failings of MACRO-economics) so that
;          determinations of the availability of hard disks, for example,
;          could be included. Also it is sometimes nice to have these
;          values at the very beginning of a program so that DDT-style
;          customizations can be made.
;       3) A SECTBUF parameter has been added to FILE to turn off the
;          standard character buffering previously provided. It seemed
;          reasonable to provide this new open machinery even when
;          simple sector buffering was intended. Also when SECTBUF=NONE
;          all buffering is turned off and only the new open code is
;          produced. This can also be done through use of the POPEN macro
;          directly(without FCB's being generated).

FILERR  SET     0000H   ;REBOOT AFTER ERROR
@FALSE  SET     0000H
@TRUE   SET     NOT @FALSE
@BDOS   EQU     0005H   ;BDOS ENTRY POINT
@TFCB   EQU     005CH   ;DEFAULT FILE CONTROL BLOCK
@TBUF   EQU     0080H   ;DEFAULT BUFFER ADDRESS
;
;       BDOS FUNCTIONS
@MSG    EQU     9       ;SEND MESSAGE
@OPN    EQU     15      ;FILE OPEN
@CLS    EQU     16      ;FILE CLOSE
@DIR    EQU     17      ;DIRECTORY SEARCH
@DEL    EQU     19      ;FILE DELETE
@MAK    EQU     22      ;FILE MAKE
@REN    EQU     23      ;FILE RENAME
@DMA    EQU     26      ;SET DMA ADDRESS
@FRD    EQU     33      ;FILE RANDOM READ OPERATION
@FWR    EQU     34      ;FILE RANDOM WRITE OPERATION
@CFS    EQU     35      ;COMPUTE FILE SIZE
@SETRR  EQU     36      ;SET RANDOM RECORD
;
@SECT   EQU     128     ;SECTOR SIZE
EOF     EQU     1AH     ;END OF FILE
@CR     EQU     0DH     ;CARRIAGE RETURN
@LF     EQU     0AH     ;LINE FEED
TAB     EQU     09H     ;HORIZONTAL TAB
;
@KEY    EQU     1       ;KEYBOARD
@CON    EQU     2       ;CONSOLE DISPLAY
@RDR    EQU     3       ;READER
@PUN    EQU     4       ;PUNCH
@LST    EQU     5       ;LIST DEVICE
;
;       KEYWORDS FOR "FILE" MACRO
NONE    EQU     1
SECTBUFF        EQU     @TRUE
NONLOC  EQU     @TRUE
INFILE  EQU     1       ;INPUT FILE
OUTFILE EQU     2       ;OUTPUTFILE
SETFILE EQU     3       ;SETUP NAME ONLY
APPEND  EQU     4       ;APPEND TO FILE
;
;       THE FOLLOWING MACROS DEFINE SIMPLE SEQUENTIAL
;       FILE OPERATIONS:
;
FILLNAM MACRO   FC,C
;;      FILL THE FILE NAME/TYPE GIVEN BY FC FOR C CHARACTERS
@CNT    SET     C       ;;MAX LENGTH
       IRPC    ?FC,FC  ;;FILL EACH CHARACTER
;;      MAY BE END OF COUNT OR NUL NAME
       IF      @CNT=0 OR NUL ?FC
       EXITM
       ENDIF
       DB      '&?FC'  ;;FILL ONE MORE
@CNT    SET     @CNT-1  ;;DECREMENT MAX LENGTH
       ENDM            ;;OF IRPC ?FC
;;
;;      PAD REMAINDER
       REPT    @CNT    ;;@CNT IS REMAINDER
       DB      ' '     ;;PAD ONE MORE BLANK
       ENDM            ;;OF REPT
       ENDM
;
FILLDEF MACRO   FCB,?FL,?LN
;;      FILL THE FILE NAME FROM THE DEFAULT FCB
;;      FOR LENGTH ?LN (9 OR 12)
       LOCAL   PSUB
       JMP     PSUB    ;;JUMP PAST THE SUBROUTINE
@DEF:   ;;THIS SUBROUTINE FILLS FROM THE TFCB (+16)
       MOV     A,M     ;;GET NEXT CHARACTER TO A
       STAX    D       ;;STORE TO FCB AREA
       INX     H
       INX     D
       DCR     C       ;;COUNT LENGTH DOWN TO 0
       JNZ     @DEF
       RET
;;      END OF FILL SUBROUTINE
PSUB    EQU $
FILLDEF MACRO   ?FCB,?F,?L
       LXI     H,@TFCB+?F      ;;EITHER @TFCB OR @TFCB+16
       LXI     D,?FCB
       MVI     C,?L            ;;LENGTH = 9,12
       CALL    @DEF
       ENDM
       FILLDEF FCB,?FL,?LN
       ENDM
;
FILLNXT MACRO
;;      INITIALIZE BUFFER AND DEVICE NUMBERS
@NXTB   SET     0       ;;NEXT BUFFER LOCATION
@NXTD   SET     @LST+1  ;;NEXT DEVICE NUMBER
FILLNXT MACRO
       ENDM
       ENDM
;
FILLFCB MACRO   MD,FID,DN,FN,FT,BS,BA
;;      FILL THE FILE CONTROL BLOCK WITH DISK NAME
;;      DEFINE FILE USING MODE MD:
;;              INFILE = 1      INPUT FILE
;;              OUTFILE = 2     OUTPUT FILE
;;              SETFILE = 3     SETUP FCB
;;      FID IS AN INTERNAL NAME FOR THE FILE,
;;      DN IS THE DRIVE NAME (A,B..), OR BLANK
;;      FN IS THE FILE NAME, OR BLANK
;;      FT IS THE FILE TYPE
;;      BS IS THE BUFFER SIZE
;;      BA IS THE BUFFER ADDRESS
       LOCAL   PFCB
;;
FID&TYP SET     MD      ;;SET MODE FOR LATER REF'S
;;      SET UP THE FILE CONTROL BLOCK FOR THE FILE
;;      LOOK FOR FILE NAME = 1 OR 2
@C      SET     1       ;;ASSUME TRUE TO BEGIN WITH
       IRPC    ?C,FN   ;;LOOK THROUGH CHARACTERS OF NAME
       IF      NOT ('&?C' = '1' OR '&?C' = '2')
@C      SET     0       ;;CLEAR IF NOT 1 OR 2
       ENDIF
       ENDM
;;      @C IS TRUE IF FN = 1 OR 2 AT THIS POINT
       IF      @C      ;;THEN FN = 1 OR 2
;;      FILL FROM DEFAULT AREA
       IF      NUL FT  ;;TYPE SPECIFIED?
@C      SET     12      ;;BOTH NAME AND TYPE
       ELSE
@C      SET     9       ;;NAME ONLY
       ENDIF
       FILLDEF FCB&FID,(FN-1)*16,@C    ;;TO SELECT THE FCB
       JMP     PFCB    ;;PAST FCB DEFINITION
       DS      @C      ;;SPACE FOR DRIVE/FILENAME/TYPE
       FILLNAM FT,12-@C        ;;SERIES OF DB'S
       ELSE
       JMP     PFCB    ;;PAST INITIALIZED FCB
       IF      NUL DN
       DB      0       ;;USE DEFAULT DRIVE IF NAME IS ZERO
       ELSE
       DB      '&DN'-'A'+1     ;;USE SPECIFIED DRIVE
       ENDIF
       FILLNAM FN,8    ;;FILL FILE NAME
;;      NOW GENERATE THE FILE TYPE WITH PADDED BLANKS
       FILLNAM FT,3    ;;AND THREE CHARACTER TYPE
       ENDIF
FCB&FID EQU     $-12    ;;BEGINNING OF THE FCB
       DB      0       ;;EXTENT FIELD 00 FOR SETFILE
;;      NOW DEFINE THE 3 BYTE FIELD, AND DISK MAP
       DS      23      ;;X,X,RC,DM0...DM15,CR,R0,R1,R2 FIELDS
       DB      0FFH    ;; DEFAULT CURRENT USER AREA
;;
       IF      FID&TYP<=2      ;;IN/OUTFILE
;;      GENERATE CONSTANTS FOR INFILE/OUTFILE
       FILLNXT         ;;@NXTB=0 ON FIRST CALL
       IF      BS+0<@SECT
;;      BS NOT SUPPLIED, OR TOO SMALL
@BS     SET     @SECT   ;;DEFAULT TO ONE SECTOR
       ELSE
;;      COMPUTE EVEN BUFFER ADDRESS
@BS     SET     (BS/@SECT)*@SECT
       ENDIF
;;
;;      NOW DEFINE BUFFER BASE ADDRESS
       IF      NUL BA
;;      USE NEXT ADDRESS AFTER @NXTB
FID&BUF SET     BUFFERS+@NXTB
;;      COUNT PAST THIS BUFFER
@NXTB   SET     @NXTB+@BS
       ELSE
FID&BUF SET     BA
       ENDIF
;;      FID&BUF IS BUFFER ADDRESS
FID&ADR EQU $
       DW      FID&BUF
;;
FID&SIZ EQU     @BS     ;;LITERAL SIZE
FID&LEN EQU $
       DW      @BS     ;;BUFFER SIZE
FID&PTR EQU $
       DS      2       ;;SET IN INFILE/OUTFILE
;;      SET DEVICE NUMBER
@&FID   SET     @NXTD   ;;NEXT DEVICE
@NXTD   SET     @NXTD+1
       ENDIF   ;;OF FID&TYP<=2 TEST
PFCB    EQU $
       ENDM
;
FILE    MACRO   FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF
;;      (SEE FILLFCB FOR PARAMETERS)
FID&FLG SET     1
       IF NUL PU
FID&PUB SET     0
       ELSE
FID&PUB SET     1
       ENDIF

@SETRC  SET     @SETRR
       IF      FMODE=APPEND
@SETRC  SET     @CFS
       GFILE   FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,0
FID&TYP SET     OUTFILE         ;;SET MODE FOR LATER REF'S
       ENDIF
       GFILE   FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,@SETRC
       ENDM
;
GFILE   MACRO   FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,@SETRC
       LOCAL   PSUB,MSG,PMSG
       LOCAL   PND,EOD,EOB,PNC,GLOOP,SAMEUSR
;;      CONSTRUCT THE FILE CONTROL BLOCK
;;
MD      SET     FMODE
       IF      FMODE=APPEND
       IF      @SETRC=0
MD      SET     INFILE
       ELSE
MD      SET     OUTFILE
       ENDIF
       ENDIF
       IF      FID&FLG
       FILLFCB MD,FID,DN,FN,FT,BS,BA
       ENDIF
       IF      MD=SETFILE      ;;SETUP FCB ONLY, SO EXIT
       EXITM
       ENDIF
;;      FILE CONTROL BLOCK AND RELATED PARAMETERS
;;      ARE CREATED INLINE, NOW CREATE IO FUNCTION
BLOCKING        SET     @TRUE
       IF      NUL SECTBUF     ;;INPUT FILE
       JMP     PSUB    ;;PAST INLINE SUBROUTINE
       IF      MD=OUTFILE
PUT&FID EQU $
       PUSH    PSW     ;;SAVE OUTPUT CHARACTER
       ELSE
GET&FID EQU $
       ENDIF
       LHLD    FID&LEN ;;LOAD CURRENT BUFFER LENGTH
       XCHG            ;;DE IS LENGTH
       LHLD    FID&PTR ;;LOAD NEXT TO GET/PUT TO HL
       MOV     A,L     ;;COMPUTE CUR-LEN
       SUB     E
       MOV     A,H
       SBB     D       ;;CARRY IF NEXT<LENGTH
       JC      PNC     ;;CARRY IF LEN GTR CURRENT
;;      END OF BUFFER, FILL/EMPTY BUFFERS
       ELSE
       IF      SECTBUF=NONE
BLOCKING        SET     @FALSE
       ENDIF
       ENDIF
       IF      BLOCKING
       LXI     H,0
       SHLD    FID&PTR ;;CLEAR NEXT TO GET/PUT
PND     EQU $
;;      PROCESS NEXT DISK SECTOR:
       XCHG            ;;FID&PTR TO DE
       LHLD    FID&LEN ;;DO NOT EXCEED LENGTH
;;      DE IS NEXT TO FILL/EMPTY, HL IS MAX LEN
       MOV     A,E     ;;COMPUTE NEXT-LEN
       SUB     L       ;;TO GET CARRY IF MORE
       MOV     A,D
       SBB     H       ;;TO FILL
       JNC     EOB
;;      CARRY GEN'ED, HENCE MORE TO FILL/EMPTY
       LHLD    FID&ADR ;;BASE OF BUFFERS
       DAD     D       ;;HL IS NEXT BUFFER ADDR
       XCHG
       MVI     C,@DMA  ;;SET DMA ADDRESS
       CALL    @BDOS   ;;DMA ADDRESS IS SET
       IF      FID&PUB
       LDA     FCB&FID+36      ;; GET USER AREA OF FILE
       CPI     0FFH
       JZ      SAMEUSR
       MVI     C,32
       MOV     E,A
       CALL    @BDOS           ;; GO TO FILE USER AREA
SAMEUSR EQU     $
       ENDIF
       LXI     D,FCB&FID       ;;FCB ADDRESS TO DE
       IF      MD=INFILE       ;;READ BUFFER FUNCTION
       MVI     C,@FRD  ;;FILE READ FUNCTION
       ELSE
       MVI     C,@FWR  ;;FILE WRITE FUNCTION
       ENDIF
       CALL    @BDOS   ;;RD/WR TO/FROM DMA ADDRESS
       IF      FID&PUB
       CALL    RESET$SYSTEM
       ENDIF
       ORA     A       ;;CHECK RETURN CODE
       JNZ     EOD     ;;END OF FILE/DISK?
;;      NOT END OF FILE/DISK, INCREMENT LENGTH
       LHLD    FCB&FID+33      ;;INDEX TO RANDOM RECORD #
       INX     H
       SHLD    FCB&FID+33      ;;POINTER UPDATED
       LXI     D,@SECT ;;SECTOR SIZE
       LHLD    FID&PTR ;;NEXT TO FILL
       DAD     D
       SHLD    FID&PTR ;;BACK TO MEMORY
       JMP     PND     ;;PROCESS ANOTHER SECTOR
;;
EOD     EQU $
;;      END OF FILE/DISK ENCOUNTERED
       IF      MD=INFILE       ;;INPUT FILE
       LHLD    FID&PTR ;;LENGTH OF BUFFER
       SHLD    FID&LEN ;;RESET LENGTH
       ELSE
;;      FATAL ERROR, END OF DISK
       LOCAL   EMSG
       MVI     C,@MSG  ;;WRITE THE ERROR
       LXI     D,EMSG
       CALL    @BDOS   ;;ERROR TO CONSOLE
       POP     PSW     ;;REMOVE STACKED CHARACTER
       JMP     FILERR  ;;USUALLY REBOOTS
EMSG    EQU $
       DB      @CR,@LF
       DB      'DISK FULL: &FID'
       DB      '$'
       ENDIF
;;
EOB     EQU $
;;      END OF BUFFER, RESET DMA AND POINTER
       LXI     D,@TBUF
       MVI     C,@DMA
       CALL    @BDOS
       LXI     H,0
       SHLD    FID&PTR ;;NEXT TO GET
;;
PNC     EQU $
       IF      NUL SECTBUF
;;      PROCESS THE NEXT CHARACTER
       XCHG            ;;INDEX TO GET/PUT IN DE
       LHLD    FID&ADR ;;BASE OF BUFFER
       DAD     D       ;;ADDRESS OF CHAR IN HL
       XCHG            ;;ADDRESS OF CHAR IN DE
       IF      MD=INFILE       ;;INPUT PROCESSING DIFFERS
       LHLD    FID&LEN ;;FOR EOF CHECK
       MOV     A,L     ;;0000?
       ORA     H
       MVI     A,EOF   ;;END OF FILE?
       RZ              ;;ZERO FLAG IF SO
       LDAX    D       ;;NEXT CHAR IN ACCUM
       ELSE
;;      STORE NEXT CHARACTER FROM ACCUMULATOR
       POP     PSW     ;;RECALL SAVED CHAR
       STAX    D       ;;CHARACTER IN BUFFER
       ENDIF
       LHLD    FID&PTR ;;INDEX TO GET/PUT
       INX     H
       SHLD    FID&PTR ;;POINTER UPDATED
;;      RETURN WITH NON ZERO FLAG IF GET
       ENDIF
       RET
       ENDIF           ; IF BLOCKING
;;
PSUB    EQU $
       IF      FID&FLG
       ;;PAST INLINE SUBROUTINE
       XRA     A               ;;ZERO TO ACC
       STA     FCB&FID+12      ;;CLEAR EXTENT
       STA     FCB&FID+32      ;;CLEAR CUR REC
       LXI     H,FID&SIZ       ;;BUFFER SIZE
       SHLD    FID&LEN         ;;SET BUFF LEN
       IF      MD=INFILE       ;;INPUT FILE
       SHLD    FID&PTR ;;CAUSE IMMEDIATE READ
       ELSE            ;;OUTPUT FILE
       LXI     H,0     ;;SET NEXT TO FILL
       SHLD    FID&PTR ;;POINTER INITIALIZED
       MVI     C,@DEL
       LXI     D,FCB&FID       ;;DELETE FILE
       CALL    @BDOS   ;;TO CLEAR EXISTING FILE
       MVI     C,@MAK  ;;CREATE A NEW FILE
       ENDIF
;;      NOW OPEN (IF INPUT), OR MAKE (IF OUTPUT)
       LXI     D,FCB&FID
LOCALT  SET     NUL NOLOC
       IF      NOT FID&PUB OR LOCALT
       PUSH    D
       MVI     C,@OPN  ;;OPEN FILE FUNCTION
       CALL    @BDOS   ;;OPEN/MAKE OK?
       INR     A       ;;255 BECOMES 00
       POP     D
       JNZ     PMSG
       ENDIF           ; NUL NOLOC OR NUL PU
       IF      FID&PUB AND MD=INFILE
       POPEN   NOLOC
       JNZ     PMSG
       ENDIF
       IF      FMODE=APPEND
       MVI     A,EOF   ;; PRIME THE BUFFER
       STA     FID&BUF
       LXI     H,0     ;;SET NEXT TO FILL
       SHLD    FID&PTR ;;POINTER INITIALIZED
       LXI     D,FCB&FID
       MVI     C,@MAK
       CALL    @BDOS
       INR     A       ;;255 BECOMES 00
       JNZ     PMSG
       ENDIF
       MVI     C,@MSG  ;;PRINT MESSAGE FUNCTION
       LXI     D,MSG   ;;ERROR MESSAGE
       CALL    @BDOS   ;;PRINTED AT CONSOLE
       JMP     FILERR  ;;TO RESTART
MSG     EQU $
       DB      @CR,@LF
       IF      MD=INFILE AND NOT (FMODE=APPEND)        ;;INPUT MESSAGE
       DB      'NO &FID FILE'
       ELSE
       DB      'NO DIR SPACE: &FID'
       ENDIF
       DB      '$'

       IF      @SETRC=0
BACK&FID        EQU     $
       LXI     H,FID&SIZ       ;;RESET THE LENGTH, IT MAY BE ZERO
       SHLD    FID&LEN         ;;IF NO EOF CHARACTER WAS FOUND
       LHLD    FID&PTR         ;;GET INDEX TO GET/PUT
       MOV     A,L             ;;IF =0000 NO EOF CHARACTER TO BACK UP OVER
       ORA     H
       RZ
       DCX     H
       SHLD    FID&PTR ;;POINTER UPDATED
@@&FID  EQU     $
       LHLD    FCB&FID+33      ;;INDEX TO RANDOM RECORD #
       MOV     A,L             ;;=0000? BE SURE WE DON'T GO BELOW
       ORA     H
       RZ
       DCX     H
       SHLD    FCB&FID+33      ;;POINTER UPDATED
       RET
       ENDIF
PMSG    EQU $
       ENDIF
       IF      NOT (@SETRC=0)
       MVI     C,@SETRC        ; GET RANDOM RECORD #
       LXI     D,FCB&FID
       CALL    @BDOS
       IF      FMODE=APPEND
       CALL    @@&FID
GLOOP   EQU     $               ; MOVE TO EOF IN LAST RECORD
       CALL    GET&FID
       CPI     EOF
       JNZ     GLOOP
       CALL    BACK&FID
       ENDIF           ; FMODE=APPEND
       IF      FID&PUB
       CALL    RESET$SYSTEM
       ENDIF           ; FID&PUB
       ENDIF           ; @SETRC
FID&FLG SET     0
       ENDM
;
PUT     MACRO   DEV
;;      WRITE CHARACTER FROM ACCUM TO DEVICE
       IF      @&DEV <= @LST
;;      SIMPLE OUTPUT
       PUSH    PSW     ;;SAVE CHARACTER
       MVI     C,@&DEV ;;WRITE CHAR FUNCTION
       MOV     E,A     ;;READY FOR OUTPUT
       CALL    @BDOS   ;;WRITE CHARACTER
       POP     PSW     ;;RESTORE FOR TESTING
       ELSE
       CALL    PUT&DEV
       ENDM
;
FINIS   MACRO   FID
;;      CLOSE THE FILE(S) GIVEN BY FID
       IRP     ?F,<FID>
;;      SKIP ALL BUT OUTPUT FILES
       IF      ?F&TYP=OUTFILE
       LOCAL   EOB?,PEOF,MSG,PMSG,SAMEUSR
;;      WRITE ALL PARTIALLY FILLED BUFFERS
EOB?    EQU $
       ;;ARE WE AT THE END OF A BUFFER?
       LHLD    ?F&PTR  ;;NEXT TO FILL
       MOV     A,L     ;;ON BUFFER BOUNDARY?
       ANI     (@SECT-1) AND 0FFH
       JNZ     PEOF    ;;PUT EOF IF NOT 00
       IF      @SECT>255
;;      CHECK HIGH ORDER BYTE ALSO
       MOV     A,H
       ANI     (@SECT-1) SHR 8
       JNZ     PEOF    ;;PUT EOF IF NOT 00
       ENDIF
;;      ARRIVE HERE IF END OF BUFFER, SET LENGTH
;;      AND WRITE ONE MORE BYTE TO CLEAR BUFFS
       SHLD    ?F&LEN  ;;SET TO SHORTER LENGTH
PEOF    EQU $
       MVI     A,EOF   ;;WRITE ANOTHER EOF
       PUSH    PSW     ;;SAVE ZERO FLAG
       CALL    PUT&?F
       POP     PSW     ;;RECALL ZERO FLAG
       JNZ     EOB?    ;;NON ZERO IF MORE
;;      BUFFERS HAVE BEEN WRITTEN, CLOSE FILE
       IF      ?F&PUB
       LDA     FCB&?F+36       ;; GET USER AREA OF FILE
       CPI     0FFH
       JZ      SAMEUSR
       MVI     C,32
       MOV     E,A
       CALL    @BDOS           ;; GO TO FILE USER AREA
SAMEUSR EQU     $
       ENDIF
       LXI     D,FCB&?F        ;;FCB ADDRESS TO DE
       MVI     C,@CLS
       CALL    @BDOS           ;; CLOSE THE FILE
       IF      ?F&PUB
       CALL    RESET$SYSTEM
       ENDIF
       INR     A       ;;255 IF ERR BECOMES 00
       JNZ     PMSG
;;      FILE CANNOT BE CLOSED
       MVI     C,@MSG
       LXI     D,MSG
       CALL    @BDOS
       JMP     PMSG    ;;ERROR MESSAGE PRINTED
MSG     EQU $
       DB      @CR,@LF
       DB      'CANNOT CLOSE &?F'
       DB      '$'
PMSG    EQU $
       ENDIF
       ENDM    ;;OF THE IRP
       ENDM
;
ERASE   MACRO   FID
;;      DELETE THE FILE(S) GIVEN BY FID
       IRP     ?F,<FID>
       MVI     C,@DEL
       LXI     D,FCB&?F
       CALL    @BDOS
       ENDM    ;;OF THE IRP
       ENDM
;
DIRECT  MACRO   FID
;;      PERFORM DIRECTORY SEARCH FOR FILE
;;      SETS ZERO FLAG IF NOT PRESENT
       LXI     D,FCB&FID
       MVI     C,@DIR
       CALL    @BDOS
       INR     A       ;00 IF NOT PRESENT
       ENDM
;
RENAME  MACRO   NEW,OLD
;;      RENAME FILE GIVEN BY "OLD" TO "NEW"
       LOCAL   PSUB,REN0
;;      INCLUDE THE RENAME SUBROUTINE ONCE
       JMP     PSUB
@RENS   EQU $
       ;;RENAME SUBROUTINE, HL IS ADDRESS OF
       ;;OLD FCB, DE IS ADDRESS OF NEW FCB
       PUSH    H       ;;SAVE FOR RENAME
       LXI     B,16    ;;B=00,C=16
       DAD     B       ;;HL = OLD FCB+16
REN0    EQU $
       LDAX    D       ;;NEW FCB NAME
       MOV     M,A     ;;TO OLD FCB+16
       INX     D       ;;NEXT NEW CHAR
       INX     H       ;;NEXT FCB CHAR
       DCR     C       ;;COUNT DOWN FROM 16
       JNZ     REN0
;;      OLD NAME IN FIRST HALF, NEW IN SECOND HALF
       POP     D       ;;RECALL BASE OF OLD NAME
       MVI     C,@REN  ;;RENAME FUNCTION
       CALL    @BDOS
       RET             ;;RENAME COMPLETE
PSUB    EQU $
RENAME  MACRO   N,O     ;;REDEFINE RENAME
       LXI     H,FCB&O ;;OLD FCB ADDRESS
       LXI     D,FCB&N ;;NEW FCB ADDRESS
       CALL    @RENS   ;;RENAME SUBROUTINE
       ENDM
       RENAME  NEW,OLD
       ENDM
;
GET     MACRO   DEV
;;      READ CHARACTER FROM DEVICE
       IF      @&DEV <= @LST
;;      SIMPLE INPUT
       MVI     C,@&DEV
       CALL    @BDOS
       ELSE
       CALL    GET&DEV
       ENDM
;
POPEN   MACRO   NOLOC
;       DE is assumed to point to the file FCB on entry
OPEN    SET     0FH
       LOCAL   PSUB,LEAVE
;OPEN MAST.CAT
*  OPTION 1:  TRY TO OPEN FILE IN CURRENT USER NUMBER ON CURRENT DISK
       JMP     PSUB
@OPEN   EQU     $
       PUSH    D       ; save the FCB
       MVI     A,0FFH  ; DECLARE CURRENT USER AREA ON FILE
       STA     FILEUA
       MVI     C,12    ; GET VERSION NUMBER
       CALL    @BDOS
       MOV     A,H     ; CP/
M 1.X?
       ORA     L
       JZ      START2$DISK     ; CHECK FOR DEFAULT DISK IF SO

*  OPTION 2:  TRY TO OPEN FILE IN USER 0 ON CURRENT DISK
       MVI     E,0FFH  ; GET CURRENT USER NUMBER
       MVI     C,32    ; GET USER CODE
       CALL    @BDOS
       MOV     C,A
       LDA     DEFAULT$USER    ; CHECK IF AT DEFAULT USER
       CMP     C
       JZ      START2$DISK     ; DON'T TRY IF AT DEFAULT USER AREA
       STA     FILEUA          ; WHERE THE FILE IS IF ANYWHERE
       MOV     E,A
       MOV     A,C
       STA     CUR$USER        ; WHERE WE ARE(SAVE FOR LATER)
       MVI     C,32    ; SET USER CODE TO DEFAULT$USER
       CALL    @BDOS
       IF      NUL NOLOC
       POP     D       ; GET BACK FCB
       PUSH    D       ; PRESERVE THE STACK
       MVI     C,OPEN
       CALL    @BDOS   ; TRY TO OPEN FILE AGAIN
       CPI     255     ; NOT PRESENT?
       JNZ     LEAVE
       ENDIF           ; NUL NOLOC
*  OPTION 3:  TRY TO OPEN FILE IN USER 0 ON DEFAULT DISK IF NOT CURRENT DISK
START2$DISK     EQU     $
       MVI     C,25    ; DETERMINE IF CURRENT DISK IS THE DEFAULT
       CALL    @BDOS
       MOV     C,A
       LDA     DEFAULT$DISK    ; CHECK IF AT DEFAULT DISK
       CMP     C
       IF      NUL NOLOC
       JZ      LEAVE           ;FAILURE TO OPEN SINCE NOTHING LEFT TO TRY
       ENDIF
       POP     H               ; FCB INTO HL
       PUSH    H               ; PRESERVE STACK
       IF      NUL NOLOC
       ELSE
       JZ      START3$DISK
       ENDIF
       INR     A               ; ADD ONE TO DISK NUMBER
       MOV     M,A     ; PUT INTO FCB
START3$DISK     EQU     $
       XCHG            ; FCB INTO DE
       MVI     C,15    ; OPEN FILE
       CALL    @BDOS
       CPI     255     ; NOT PRESENT?

LEAVE   EQU     $
       POP     D       ; GET THE FCB AGAIN(AND CLEAN UP STACK)
       PUSH    PSW     ; SAVE OPEN STATUS ON FILE
       LXI     H,36
       DAD     D
       LDA     FILEUA          ; GET THE USER AREA FOR THE FILE
       MOV     M,A             ; PUT USER AREA INTO FCB
       POP     PSW
       RET
;
RESET$SYSTEM    EQU     $
       PUSH    PSW
       LDA     CUR$USER        ; CHECK USER
       CPI     0FFH    ; 0FFH=NO CHANGE
       JZ      RESET$RET
       MOV     E,A     ; USER IN E
       MVI     C,32    ; GET/SET USER CODE
       CALL    @BDOS
RESET$RET       EQU     $
       POP     PSW
       RET

FILEUA  EQU     $
       DS      1
PSUB    EQU     $
POPEN   MACRO
       CALL    @OPEN
       ENDM
       POPEN
       ENDM