;
; PROGRAM NAME:  SUB
; AUTHOR:  RICHARD CONN (From SuperSUB Ver 1.1 by Ron Fowler)
; VERSION:  3.0
; DATE:  18 May 84
; PREVIOUS VERSIONS:  2.3 (6 Jan 83) - Called SUB2.ASM
; PREVIOUS VERSIONS:  2.2 (7 DEC 82), 2.1 (14 NOV 82), 2.0 (11 OCT 82)
; PREVIOUS VERSIONS:  1.4 (10 OCT 81), 1.3 (7 OCT 81)
; PREVIOUS VERSIONS:  1.2 (5 OCT 81), 1.1 (3 OCT 81), 1.0 (1 OCT 81)
; NOTE:  FOR USE WITH ZCPR3
;
VERS    EQU     30
z3env   SET     0f400h

;
;       SUB is derived from Ron's SuperSUB program; it provides a different
; format for the command line, a command-search hierarchy like ZCPR3, a
; resetting of the DMA address, several additional functions, and there are
; several other additions/changes.  Additionally, ZCPR3-specific enhancements,
; such as appending the rest of the multiple command line to the command file
; and allowing multiple commands on a single line, are permitted.
;
;       SuperSUB, VERSION 1.1 (09/13/81)                by Ron Fowler
;       2/18/81 (first written)                         WESTLAND, MICH.
;
;
; This program is intended as a replacement for the
; SUBMIT program provided with CP/M.  It provides sev-
; eral new facilities:
;       1) Nestable SUBMIT runs
;       2) Interactive entry of SUBMIT job (no need
;          to use an editor for simple SUBMIT runs)
;       3) Command line entry of small SUBMIT jobs
;       4) Ability to enter blank lines in an edited
;          SUBMIT file
;       5) User customization of number of parameters
;          and drive to send $$$.SUB to
;

;
; DEFINE BOOLEANS
;
FALSE   EQU     0
TRUE    EQU     NOT FALSE

;
;               --  User customizable options --
;

FORCE$SUB       EQU     FALSE   ;TRUE IF SUBMITTED FILE MUST BE OF TYPE .SUB
TIME$CONST      EQU     0C000H  ;DELAY FOR RINGING BELL
NPAR            EQU     10      ;NUMBER OF ALLOWABLE PARAMETERS
CPBASE          EQU     0       ;SET TO 4200H FOR HEATH CP/M
OPT             EQU     '/'     ;OPTION DELIMITER CHAR
PDELIM          EQU     '$'     ;PARAMETER DELIMITER

;
; SYSLIB AND Z3LIB ROUTINES
;
       EXT     Z3INIT,PFIND,GETCL1
       EXT     LOGUD,GETUD,PUTUD
       EXT     INITFCB
       EXT     PSTR,PRINT,QPRINT,COUT,CRLF,CAPS,PHLDC
       EXT     CODEND

;
; CP/M DEFINITIONS
;
FGCHAR  EQU     1       ;GET CHAR FUNCTION
DIRIOF  EQU     6       ;DIRECT CONSOLE I/O
RDBUF   EQU     10      ;READ CONSOLE BUFFER
LOGIN   EQU     14      ;LOG IN DISK
OPENF   EQU     15      ;OPEN FILE FUNCTION
CLOSEF  EQU     16      ;CLOSE FILE FUNCTION
DELETF  EQU     19      ;DELETE FILE FUNCTION
READF   EQU     20      ;READ RECORD FUNCTION
WRITEF  EQU     21      ;WRITE RECORD FUNCTION
MAKEF   EQU     22      ;MAKE (CREATE) FILE FUNCTION
GETDSK  EQU     25      ;RETURN CURRENT DISK
SETDMA  EQU     26      ;SET DMA ADDRESS
UCODE   EQU     32      ;GET/SET USER CODE
;
UDFLAG  EQU     CPBASE+4
BDOS    EQU     CPBASE+5
;
CURIND  EQU     '$'     ;CURRENT USER/DISK INDICATOR
FCB     EQU     5CH     ;DEFAULT FILE CONTROL BLOCK
FCBEX   EQU     12      ;FCB OFFSET TO EXTENT FIELD
FCBRC   EQU     15      ;FCB OFFSET TO RECORD COUNT
FCBNR   EQU     32      ;FCB OFFSET TO NEXT RECORD
FN      EQU     1       ;FCB OFFSET TO FILE NAME
FT      EQU     9       ;FCB OFFSET TO FILE TYPE
TBUF    EQU     CPBASE+80H      ;DEFAULT BUFFER
TPA     EQU     CPBASE+100H     ;TRANSIENT PROGRAM AREA
;
PUTCNT  EQU     TBUF    ;COUNTER FOR OUTPUT CHARS
;
; DEFINE SOME TEXT CHARACTERS
;
CTRLC   EQU     'C'-'@'
CTRLZ   EQU     'Z'-'@'
BEL     EQU     7       ;RING BELL
CR      EQU     13      ;CARRIAGE RETURN
LF      EQU     10      ;LINE FEED
TAB     EQU     9

;
; Environment Definition
;
       if      z3env ne 0
;
; External ZCPR3 Environment Descriptor
;
       jmp     start
       db      'Z3ENV' ;This is a ZCPR3 Utility
       db      1       ;External Environment Descriptor
z3eadr:
       dw      z3env
start:
       lhld    z3eadr  ;pt to ZCPR3 environment
;
       else
;
; Internal ZCPR3 Environment Descriptor
;
       MACLIB  Z3BASE.LIB
       MACLIB  SYSENV.LIB
z3eadr:
       jmp     start
       SYSENV
start:
       lxi     h,z3eadr        ;pt to ZCPR3 environment
       endif

;
; Start of Program -- Initialize ZCPR3 Environment
;
       call    z3init  ;initialize the ZCPR3 Env and the VLIB Env
       LXI     H,0     ;SAVE STACK IN CASE
       DAD     SP      ;  ONLY HELP REQUESTED
       SHLD    SPSAVE  ;(NOT OTHERWISE USED)
       CALL    PUTUD   ;SAVE HOME DIR
       CALL    QPRINT
       DB      'SUB  Version ',VERS/10+'0','.',(VERS MOD 10)+'0',0

       CALL    CODEND  ;SET UP EXTERNAL BUFFERS
       SHLD    CLBUF   ;SET PTR
       MVI     M,128   ;ALLOW 128 CHARS
       LXI     D,100H  ;FREE SPACE
       DAD     D       ;PT TO FREE AREA
       SHLD    FREMEM  ;SET PTR TO FREE MEMORY AREA
       SPHL            ;SET STACK PTR

       LDA     FCB+1   ;ANYTHING ON CMD LINE?
       CPI     ' '
       JZ      HELP    ;NO, GO PRINT HELP
       CALL    INITVAR ;INITIALIZE THE VARIABLE AREA
       CALL    GETPAR  ;GET COMMAND LINE PARAMETERS AND EXTRACT OPTION
       CALL    ABORT   ;PERFORM ABORT IF FLAG SET
       CALL    SETUP   ;SET UP READ OF SUBMIT FILE
       CALL    RDFILE  ;READ THE SUBMIT FILE
       CALL    WRSET   ;SET UP WRITE OF "$$$.SUB"
       CALL    WRSUB   ;WRITE "$$$.SUB"
       JMP     CPBASE  ;GO START THE SUBMIT
;
;       SETUP SETS UP THE FILE CONTROL BLOCK
;       FOR READING IN THE .SUB TEXT FILE
;
SETUP:
       LXI     H,FCB+FT        ;LOOK AT FIRST CHAR OF
       MOV     A,M             ;FILE TYPE.  IF IT IS
       CPI     ' '             ;BLANK, THEN GO MOVE
       JZ      PUTSUB          ;"SUB" INTO FT FIELD

       IF      FORCE$SUB       ;FILE TYPE MUST BE OF .SUB
       LXI     D,SUBTYP        ;FILE TYPE MUST BE .SUB
       MVI     B,3             ;3 BYTES
       CALL    COMPAR
       JNZ     NOTFND  ;FILE NOT FOUND IF NO TYPE MATCH
       ENDIF

       RET             ;  IF NOT BLANK, THEN ACCEPT ANY FILE TYPE
;
;       MOVE "SUB" INTO THE FILE TYPE
;
PUTSUB:
       XCHG            ;BY CONVENTION, MOVE FROM
       LXI     H,SUBTYP        ; @HL TO @DE
       MVI     B,3
       CALL    MOVE
       RET
;
; MOVE # BYTES IN B REGISTER FROM @HL TO @DE
;
MOVE:
       MOV     A,M     ;PICK UP
       STAX    D       ;PUT DOWN
       INX     H       ;I'M SURE
       INX     D       ; YOU'VE SEEN THIS
       DCR     B       ; BEFORE...
       JNZ     MOVE    ;100 TIMES AT LEAST
       RET             ;I KNOW I HAVE!
;
; GETPAR MOVES THE SUBSTITUTION PARAMETERS SPECIFIED
; IN THE COMMAND LINE INTO MEMORY, AND STORES THEIR
; ADDRESSES IN THE PARAMETER TABLE.  THIS ALLOWS
; SUBSTITUTION OF $1, $2, ETC., IN THE SUBMIT COMMANDS
; WITH THEIR ACTUAL VALUES SPECIFED IN THE COMMAND
; LINE.
;
GETPAR:
       XRA     A       ;A=0
       STA     AFLAG   ;TURN OFF ABORT COMMAND
       LXI     H,TBUF+1        ;WHERE WE FIND THE COMMAND TAIL
       CALL    SCANTO  ;SKIP SUBMIT FILE NAME
       STA     OPTION  ;FIRST CHAR OF CMD LINE IS OPTION
       RC              ;LINE ENDED?
       CPI     OPT     ;NO, CHECK OPTION
       JNZ     GLP0    ;NOT KEYBOARD INP, READ FILE
       INX     H       ;POINT PAST '/'
       MOV     A,M     ;GET OPTION CHAR
       CPI     'A'     ;ABORT COMMAND
       JZ      GPARABT
       CPI     'I'     ;INTERACTIVE MODE
       RZ              ;RETURN IF SO
       JMP     HELP    ;HELP OTHERWISE
GPARABT:
       MVI     A,0FFH  ;TURN ON ABORT FLAG
       STA     AFLAG
       INX     H       ;GET POSSIBLE BELL OPTION
       MOV     A,M
       CPI     'B'     ;BELL OPTION
       RNZ
       MVI     A,0FFH  ; SET BELL FLAG
       STA     BELL$FLAG
       RET
GLP0:
       MOV     A,M     ;INPUT IS FROM A .SUB FILE..THIS
       INX     H       ;  CODE SKIPS OVER THE NAME OF
       ORA     A       ;  THE SUB FILE TO GET TO THE
       RZ              ;  COMMAND LINE PARAMETERS
       CPI     ' '
       JZ      GLP
       CPI     TAB
       JNZ     GLP0
GLP:
       CALL    SCANTO  ;PASS UP THE BLANKS
       RC              ;CY RETURNED IF END OF CMD LINE
       CALL    PUTPAR  ;NOW PUT THE PARAMETER INTO MEM
       RC              ;CY RETURNED IF END OF CMD LINE
       JMP     GLP     ;GET THEM ALL
;
; SCANTO SCANS PAST BLANKS TO THE FIRST NON-BLANK. IF
; END OF COMMAND LINE FOUND, RETURNS CARRY SET.
;
SCANTO:
       MOV     A,M
       INX     H
       ORA     A       ;SET FLAGS ON ZERO
       STC             ;IN CASE ZERO FOUND (END OF CMD LIN)
       RZ
       CPI     ' '
       JZ      SCANTO  ;SCAN PAST BLANKS
       CPI     TAB     ;DO TABS TOO, JUST FOR
       JZ      SCANTO  ;  GOOD MEASURE
       DCX     H       ;FOUND CHAR, POINT BACK TO IT
       ORA     A       ;INSURE CARRY CLEAR
       RET
;
; PUTPAR PUTS THE PARAMETER POINTED TO BY HL INTO
; MEMORY POINTED TO BY "TXTPTR".  ALSO STORES THE
; ADDRESS OF THE PARAMETER INTO THE PARAMETER TABLE
; FOR EASY ACCESS LATER, WHEN WE WRITE $$$.SUB
;
PUTPAR:
       PUSH    H       ;SAVE POINTER TO PARM
       LHLD    TXTPTR  ;NEXT FREE MEMORY
       XCHG            ;  INTO DE
       LHLD    TBLPTR  ;NEXT FREE AREA OF TABLE
       MOV     A,M     ;NON-ZERO IN TABLE
       ORA     A       ; INDICATES TABLE
       JNZ     PAROVF  ; TABLE OVERFLOW (TOO MANY PARMS)
       MOV     M,E     ;STORE THE PARM ADRS
       INX     H
       MOV     M,D
       INX     H
       SHLD    TBLPTR  ;SAVE TABLE PNTR FOR NEXT TIME
       POP     H       ;GET BACK PARM POINTER
       PUSH    D       ;SAVE FREE MEM POINTER BECAUSE
                       ;  WE WILL HAVE TO HAVE IT BACK
                       ;  LATER TO STORE THE LENGTH
       INX     D       ;POINT PAST LENGTH STORAGE
       MVI     B,0     ;INITIALIZE LENGTH OF PARM
PPLP:
       MOV     A,M     ;GET NEXT BYTE OF PARM
       INX     H
       ORA     A       ;TEST FOR END OF CMD LINE
       JZ      PP2     ;JUMP IF END
       CPI     ' '     ;TEST FOR END OF COMMAND
       JZ      PP2
       CPI     TAB     ;TAB ALSO ENDS COMMAND
       JZ      PP2
       STAX    D       ;PUT PARAMETER BYTE-BY-BYTE
       INX     D       ;INTO FREE MEMORY
       INR     B       ;BUMP LENGTH
       JMP     PPLP
PP2:
       XCHG
       SHLD    TXTPTR  ;NEW FREE MEMORY POINTER
       POP     H       ;REMEMBER OUR LENGTH POINTER?
       MOV     M,B     ;STORE THE LENGTH
       XCHG            ;HAVE TO RETN HL > CMD LINE
       ORA     A       ;NOW RETURN END OF LINE FLAG
       STC
       RZ              ;RETURN CY IF ZERO (EOL MARK)
       CMC
       RET
;
;
;       ABORT CHECKS TO SEE IF THE ABORT FLAG IS SET AND
;       EXECUTES THE ABORT FUNCTION IF SO
;
;
ABORT:
       LDA     AFLAG   ;GET THE FLAG
       ORA     A       ;0=NO
       RZ
       CALL    PRINT
       DB      CR,LF,' Strike ^C to Abort Command File - ',0
       CALL    CHARINB ;GET RESPONSE
       CPI     CTRLC   ;ABORT?
       JNZ     ABORT1  ;RETURN TO OPSYS
ABORT0:
       LXI     D,SUBFCB        ;DELETE SUBMIT FILE
       MVI     C,DELETF
       CALL    BDOS
       CALL    PRINT
       DB      ' ... Aborted',0
       JMP     CPBASE  ;RETURN TO CP/M
ABORT1:
       CALL    PRINT
       DB      ' ... Continuing',0
       JMP     CPBASE  ; RETURN TO CP/M
;
;       INPUT CHAR FROM CON:; RING BELL EVERY SO OFTEN IF FLAG SET
;
CHARINB:
       LDA     BELL$FLAG       ; GET FLAG
       ORA     A               ; 0=NO
       JZ      CHARIN
       PUSH    H               ; SAVE HL
CHARINB$LOOP:
       LXI     H,TIME$CONST    ; GET TIME CONSTANT
CHARINB$LOOP1:
       XTHL                    ; LONG DELAY
       XTHL
       DCX     H               ; COUNT DOWN
       MOV     A,H
       ORA     L
       JNZ     CHARINB$LOOP1
       MVI     E,0FFH          ; REQUEST STATUS
       MVI     C,DIRIOF        ; DIRECT I/O
       CALL    BDOS
       ORA     A               ; ANY INPUT?
       JNZ     CHARINB$DONE
       MVI     E,BEL           ; RING BELL
       MVI     C,DIRIOF
       CALL    BDOS
       JMP     CHARINB$LOOP
CHARINB$DONE:
       POP     H               ; RESTORE HL
       JMP     CAPS            ; CAPITALIZE CHAR

;
;       INPUT CHAR FROM CON:; CAPITALIZE IT AND ECHO <CRLF>
;
CHARIN:
       MVI     C,FGCHAR        ;GET CHAR
       CALL    BDOS
       JMP     CAPS            ;CAPITALIZE
;
;       RDFILE READS THE .SUB FILE SPECIFIED
;       IN THE SUBMIT COMMAND INTO MEMORY
;
RDFILE:
       LXI     H,0     ;INIT LINE NUMBER
       SHLD    LINNUM
       LDA     OPTION  ;USING A FILE?
       CPI     OPT     ;OPT OPTION TELLS
       JNZ     RDFILE1 ;JUMP IF NOT
       CALL    PRINT
       DB      CR,LF,' Input Command Lines',0
       CALL    CLFILL  ;GET FIRST LINE
       JMP     LINE
RDFILE1:
       CALL    PRINT
       DB      CR,LF,' Processing SUB File',0

;  CHECK FOR .SUB FILE IN CURRENT USER/CURRENT DISK
       LXI     D,FCB   ;WE ARE, OPEN IT
       CALL    INITFCB ;INIT FCB
       MVI     A,0FFH  ;SEARCH CURRENT ALSO
       CALL    PFIND   ;LOOK FOR FILE
       JZ      NOTFND  ;FILE NOT FOUND
       CALL    LOGUD   ;LOG INTO DIRECTORY
       LXI     D,FCB   ;PT TO FCB
       MVI     C,OPENF ;OPEN FILE
       CALL    BDOS
       CALL    FILL    ;READ FIRST BLOCK
       JNZ     NOTEXT  ;EMPTY FILE
LINE:
       LHLD    LINNUM  ;BUMP LINE NUMBER
       INX     H
       SHLD    LINNUM
       LHLD    PREV    ;GET PREV PREVIOUS LINE POINTER
       XCHG
       LHLD    TXTPTR  ;GET CURRENT FREE MEM POINTER
       SHLD    PREV    ;MAKE IT THE PREV LINE (FOR NXT PASS)
       MOV     M,E     ;STORE AT BEGIN OF CURRENT LINE,
       INX     H       ;  A POINTER TO THE PREVIOUS
       MOV     M,D
       INX     H
       PUSH    H       ;LATER WE WILL PUT LENGTH HERE
       INX     H       ;SKIP PAST LENGTH
       MVI     C,0     ;INITIALIZE LENGTH TO ZERO
LLP:
       CALL    GNB     ;GET NEXT BYTE FROM INPUT SOURCE
       CPI     CTRLZ   ;END OF FILE?
       JZ      EOF     ;CY SET IF END OF FILE FOUND
       ANI     7FH     ;MASK OUT MSB
       CALL    CAPS    ;CONVERT TO UPPER CASE
       CPI     LF      ;IGNORE LINEFEEDS
       JZ      LLP
       CPI     CR      ;IF IT'S A CARRIAGE RETURN,
       JZ      EOL     ;  THEN DO END OF LINE
       MOV     M,A     ;STORE ALL OTHERS INTO MEMORY
       INX     H
       CALL    SIZE    ;MAKE SURE NO MEMORY OVERFLOW
       INR     C       ;BUMP CHAR COUNT
       JM      LENERR  ;MAX OF 128 CHARS PER LINE
       JMP     LLP     ;GO DO NEXT CHAR
;
;       DO END OF LINE SEQUENCE
;
EOL:
       SHLD    TXTPTR  ;SAVE FREE MEMORY POINTER
       POP     H       ;CURRENT LINE'S LENGTH POINTER
       MOV     M,C     ;STORE LENGTH AWAY
       JMP     LINE    ;GO DO NEXT LINE
;
;       END OF TEXT FILE
;
EOF:
       SHLD    TXTPTR  ;SAVE FREE MEMORY POINTER
       PUSH    B       ;SAVE LINE LENGTH
       CALL    ZMCL    ;LOAD REST OF COMMAND LINE
       POP     B       ;RESTORE LINE LENGTH
       POP     H       ;CURRENT LINE'S LENGTH POINTER
       MOV     M,C     ;STORE LENGTH AWAY
       RET             ;ALL DONE READING SUB FILE
;
;  COPY COMMAND LINE INTO MEMORY BUFFER
;
ZMCL:
       CALL    GETCL1  ;GET ADDRESS OF COMMAND LINE BUFFER
       MOV     A,H     ;CHECK FOR ANY
       ORA     L
       RZ
       LHLD    LINNUM  ;BUMP LINE NUMBER
       INX     H
       SHLD    LINNUM
       LHLD    PREV    ;GET PREV PREVIOUS LINE POINTER
       XCHG
       LHLD    TXTPTR  ;GET CURRENT FREE MEM POINTER
       SHLD    PREV    ;MAKE IT THE PREV LINE (FOR NXT PASS)
       MOV     M,E     ;STORE AT BEGIN OF CURRENT LINE,
       INX     H       ;  A POINTER TO THE PREVIOUS
       MOV     M,D
       INX     H
       PUSH    H       ;LATER WE WILL PUT LENGTH HERE
       INX     H       ;SKIP PAST LENGTH
       MVI     C,0     ;INITIALIZE LENGTH TO ZERO
       XCHG            ;DE PTS TO NEXT PLACE TO STORE A BYTE
       CALL    GETCL1  ;GET ADDRESS OF COMMAND LINE BUFFER
       MOV     A,M     ;GET LOW
       INX     H
       MOV     H,M     ;GET HIGH
       MOV     L,A     ;HL PTS TO FIRST BYTE OF MULTIPLE COMMAND LINE
       MOV     B,M     ;GET FIRST CHAR IN LINE
       MVI     M,0     ;CLEAR LINE
       MOV     A,B     ;CHECK TO SEE IF FIRST CHAR IS A SEMICOLON (CMD SEP)
       CPI     ';'
       JNZ     ZMCL0
       INX     H       ;PT TO 2ND CHAR
       MOV     A,M     ;FIRST WAS A SEMICOLON, SO GET SECOND
ZMCL0:
       XCHG            ;HL PTS TO NEXT BUFFER SPACE, DE PTS TO MC LINE
       JMP     ZMCL1A  ;A=FIRST CHAR IN MC LINE
;
;  MAJOR LOOP TO STORE MULTIPLE COMMAND LINE
;
ZMCL1:
       LDAX    D       ;GET NEXT BYTE FROM MULTIPLE COMMAND LINE
ZMCL1A:
       ORA     A       ;0=EOL
       JZ      ZMCL2
       ANI     7FH     ;MASK OUT MSB
       CALL    CAPS    ;CONVERT TO UPPER CASE
       MOV     M,A     ;STORE CHAR INTO MEMORY
       INX     H       ;PT TO NEXT CHAR
       INX     D
       CALL    SIZE    ;MAKE SURE NO MEMORY OVFL
       INR     C       ;INCR CHAR COUNT
       JM      LENERR  ;MAX OF 128 CHARS IN LINE
       JMP     ZMCL1
;
;  DONE WITH INPUT OF MULTIPLE COMMAND LINE -- SAVE CHAR CNT AND SET PTR
;
ZMCL2:
       SHLD    TXTPTR  ;SAVE PTR
       POP     H       ;PT TO CHAR COUNT POSITION
       MOV     M,C     ;STORE CHAR COUNT
       RET

;
;       GET NEXT BYTE FROM INPUT FILE OR USER
;
GNB:
       PUSH    H       ;DON'T ALTER ANYBODY
       PUSH    D
       PUSH    B
       LDA     OPTION  ;INPUT FROM .SUB FILE?
       CPI     OPT     ;TOLD BY ORIG CMD LINE OPTION
       JNZ     GNBDISK ;GET NEXT CHAR FROM DISK BUFFER IF NOT FROM USER
       CALL    GNBKBD  ;GET A BYTE FROM KBD INPUT
       JMP     GNBXIT  ;THEN LEAVE
;
;       GET NEXT BYTE FROM DISK FILE
;
GNBDISK:
       LDA     IBP     ;GET BUFFER POINTER
       CPI     128     ;NEED ANOTHER BLOCK FROM DISK?
       JC      GNBD1   ;CONTINUE
       CALL    FILL    ;GET NEXT BLOCK
       JZ      GNBD1   ;CONTINUE IF NOT EMPTY
       CALL    GETUD   ;RETURN HOME
       MVI     A,1AH   ;FAKE EOF
       JMP     GNBXIT
GNBD1:
       MOV     E,A     ;PUT OFFSET IN DE
       MVI     D,0
       INR     A       ;POINT TO NEXT BYTE
       STA     IBP     ;SAVE FOR NEXT
       LXI     H,TBUF  ;NOW OFFSET INTO BUFFER
       DAD     D
       MOV     A,M     ;GET CHAR
GNBXIT:
       POP     B       ;RESTORE EVERYBODY
       POP     D
       POP     H
       ORA     A       ;TURN ON CARRY
       RET
;
;       FILL INPUT BUFFER FROM DISK
;
FILL:
       XRA     A       ;CLEAR INPUT BUFFER PTR
       STA     IBP
       LXI     D,FCB   ;PT TO FCB
       MVI     C,READF ;BDOS READ BLOCK FUNCTION
       CALL    BDOS
       ORA     A       ;RETURN Z IF EOF
       MVI     A,0     ;SET PTR TO FIRST CHAR
       RET
;
;       GET NEXT BYTE FROM USER (KEYBOARD INPUT)
;
GNBKBD:
       LHLD    CLPTR   ;PT TO NEXT CHAR
       MOV     A,M     ;GET IT
       INX     H       ;PT TO FOLLOWING
       SHLD    CLPTR   ;RESET PTR
       CPI     CR      ;END OF LINE?
       RNZ
       CALL    CLFILL  ;GET NEW LINE
       JZ      GKEND   ;EMPTY LINE INPUT - RETURN EOF
       MVI     A,CR    ;RETURN CR TO INDICATE END OF LINE
       RET
GKEND:
       MVI     A,1AH   ;RETURN EOF
       RET
;
;       FILL THE COMMAND LINE FROM THE USER
;
CLFILL:
       CALL    PRINT
       DB      CR,LF,' Command Line? ',0
       LHLD    CLBUF   ;NOW FILL THE BUFFER
       XCHG            ;...DE PTS TO IT
       MVI     C,RDBUF
       CALL    BDOS
       LHLD    CLBUF   ;PT TO COMMAND LINE BUFFER
       INX     H
       MOV     A,M     ;GET CHAR COUNT
       INX     H
       SHLD    CLPTR   ;RESET THE COMMAND LINE PTR
       ORA     A       ;SET ZERO FLAG
       PUSH    PSW     ;SAVE A
       ADD     L       ;PT TO AFTER LAST CHAR
       MOV     L,A
       MOV     A,H
       ACI     0
       MOV     H,A
       MVI     M,CR    ;SET EOL CHAR
       POP     PSW     ;GET CHAR COUNT
       RET
;
;       MAKE SURE NO MEMORY OVERFLOW
;
SIZE:
       LDA     BDOS+2  ;HIGHEST PAGE POINTER
       SUI     9       ;MAKE IT BE UNDER CCP
       CMP     H       ;CHECK IT AGAINST CURRENT PAGE
       RNC             ;NC=ALL OKAY
       JMP     MEMERR  ;OTHERWISE ABORT
;
;       SET UP THE $$$.SUB FILE
;       FOR WRITING
;
WRSET:
       CALL    PRINT
       DB      CR,LF,' Writing Command File to Disk',0
       LXI     D,SUBFCB
       MVI     C,OPENF
       CALL    BDOS    ;OPEN THE FILE
       INR     A       ;CHECK CPM RETURN
       JZ      NONE1   ;NONE EXISTS ALREADY
;
;       $$$.SUB EXISTS, SO SET
;       FCB TO APPEND TO IT
;
       LDA     SUBFCB+FCBRC    ;GET RECORD COUNT
       STA     SUBFCB+FCBNR    ;MAKE NEXT RECORD
       RET
;
;       COME HERE WHEN NO $$$.SUB EXISTS
;
NONE1:
       LXI     D,SUBFCB
       MVI     C,MAKEF
       CALL    BDOS
       INR     A
       JZ      NOMAKE  ;0FFH=CAN'T CREATE FILE
       RET
;
;       WRITE THE "$$$.SUB" FILE
;
WRSUB:
       LHLD    PREV    ;THIS CODE SCANS BACKWARD
       MOV     A,H     ;  THRU THE FILE STORED IN
       ORA     L       ;  MEMORY TO THE FIRST NON-
       JZ      NOTEXT  ;  NULL LINE.  IF NONE IS
       MOV     E,M     ;  FOUND, ABORTS
       INX     H
       MOV     D,M     ;HERE, WE PICK UP PNTR TO PREV LINE
       INX     H       ;NOW WE POINT TO LENGTH
       XCHG            ;WE NEED TO STORE AWAY
       SHLD    PREV    ;  POINTER TO PREV LINE
       XCHG
       MOV     A,M     ;NOW PICK UP THE LENGTH
       ORA     A       ;SET Z FLAG ON LENGTH
       JNZ     WRNTRY  ;GOT LINE W/LENGTH: GO DO IT
       LHLD    LINNUM  ;NOTHING HERE, FIX LINE NUMBER
       DCX     H       ;(WORKING BACKWARD NOW)
       SHLD    LINNUM
       JMP     WRSUB
WRLOP:
       LHLD    PREV    ;GET PREV LINE POINTER
       MOV     A,H
       ORA     L       ;IF THERE IS NO PREV LINE
       JZ      CLOSE   ;  THEN WE ARE DONE
       MOV     E,M     ;ELSE SET UP PREV FOR NEXT
       INX     H       ;
PASS THRU HERE
       MOV     D,M
       INX     H
       XCHG            ;NOW STORE IT AWAY
       SHLD    PREV
       XCHG
WRNTRY:
       CALL    PUTLIN  ;WRITE THE LINE TO THE FILE
       LHLD    LINNUM  ;BUMP THE LINE NUMBER
       DCX     H       ;DOWN (WORKING BACK NOW)
       SHLD    LINNUM
       JMP     WRLOP
;
;       $$$.SUB IS WRITTEN, CLOSE THE FILE
;
CLOSE:
       LXI     D,SUBFCB
       MVI     C,CLOSEF
       JMP     BDOS
;
;       THIS SUBROUTINE WRITES A LINE
;       TO THE $$$.SUB FILE BUFFER,
;       AND FLUSHES THE BUFFER AFTER
;       THE LINE IS WRITTEN.
;
PUTLIN:
       MOV     A,M     ;PICK UP LENGTH BYTE
       INX     H       ;POINT PAST IT
       STA     GETCNT  ;MAKE A COUNT FOR "GET"
       SHLD    GETPTR  ;MAKE A POINTER FOR "GET"
       LXI     H,TBUF+1        ;TEXT GOES AFTER LENGTH
       SHLD    PUTPTR  ;MAKE POINTER FOR "PUT"
       XRA     A       ;INITIALIZE PUT COUNT
       STA     PUTCNT
       MOV     B,L     ;COUNT FOR CLEAR LOOP
CLR:
       MOV     M,A     ;ZERO OUT BUFFER LOC
       INX     H
       INR     B       ;COUNT
       JNZ     CLR
;
;       THIS LOOP COLLECTS CHARACTERS
;       FROM THE LINE STORED IN MEMORY
;       AND WRITES THEM TO THE FILE.
;       IF THE "$" PARAMETER SPECIFIER
;       IS ENCOUNTERED, PARAMETER SUB-
;       STITUTION IS DONE
;
PUTLP:
       CALL    GETCHR  ;PICK UP A CHARACTER
       JC      FLUSH   ;CY = NO MORE CHAR IN LINE
       CPI     '^'     ;CONTROL-CHAR TRANSLATE PREFIX?
       JNZ     NOTCX
       CALL    GETCHR  ;YES, GET THE NEXT
       JC      CCERR   ;ERROR: EARLY END OF INPUT
       SUI     '@'     ;MAKE IT A CONTROL-CHAR
       JC      CCERR   ;ERROR: TOO SMALL
       CPI     ' '
       JNC     CCERR   ;ERROR: TOO LARGE
NOTCX:
       CPI     PDELIM  ;PARAMETER SPECIFIER?
       JNZ     STOBYT  ;IF NOT, JUST WRITE CHAR
       LDA     OPTION  ;CHECK OPTION: '$' DOESN'T
       CPI     OPT     ;  COUNT IN OPT MODE
       MVI     A,PDELIM        ;(RESTORE THE '$')
       JZ      STOBYT
       CALL    LKAHED  ;PEEK AT NEXT CHAR
       JC      PARERR  ;LINE ENDING MEANS PARAM ERR
       CPI     PDELIM  ;ANOTHER "$"?
       JNZ     SUBS    ;IF NOT THEN GO DO SUBSTITUTION
       CALL    GETCHR  ;GET THE 2ND "$" (WE ONLY LOOKED
                       ;  AHEAD BEFORE)
STOBYT:
       CALL    PUTCHR  ;WRITE CHAR TO FILE
       JMP     PUTLP
;
;       PARAMETER SUBSTITUTION...LOOKS UP THE
;       PARAMETER # AFTER THE "$" AND PLUGS IT
;       IN IF IT EXISTS.
;
SUBS:
       CALL    NUMTST  ;IT BETTER BE A NUMBER
       JC      PARERR  ;  OTHERWISE PARAM ERROR
       MVI     B,0     ;INITIALIZE PARM #
       JMP     LPNTRY  ;WE JOIN LOOP IN PROGRESS...
SUBLP:
       CALL    LKAHED  ;LOOK AT NEXT CHAR
       JC      DOSUBS  ;IF LINE EMPTY, THEN PLUG IN PARM
       CALL    NUMTST  ;CHECK FOR NUMERIC
       JC      DOSUBS  ;DONE IF NOT
LPNTRY:
       CALL    GETCHR  ;NOW REMOVE THE CHAR FROM INPUT STREAM
       SUI     '0'     ;REMOVE ASCII BIAS
       MOV     C,A     ;SAVE IT
       MOV     A,B     ;OUR ACCUMULATED COUNT
       ADD     A       ;MULTIPLY  BY TEN
       ADD     A
       ADD     B
       ADD     A
       ADD     C       ;THEN ADD IN NEW DIGIT
       MOV     B,A     ;RESTORE COUNT
       JMP     SUBLP
;
;       PERFORM THE SUBSTITUTION
;
DOSUBS:
       MOV     A,B     ;GET PARM #
       DCR     A       ;MAKE ZERO RELATIVE
       JM      PARERR  ;OOPS
       CALL    LOOKUP  ;LOOK IT UP IN PARM TABLE
       JC      PARERR  ;IT'S NOT THERE
       MOV     B,A     ;LENGTH IN B
SUBLP1:
       INR     B       ;TEST B FOR ZERO
       DCR     B
       JZ      PUTLP   ;DONE
       MOV     A,M     ;GET CHAR OF REAL PARAMETER
       INX     H       ;POINT PAST FOR NEXT TIME
       PUSH    H       ;SAVE REAL PARM POINTER
       CALL    PUTCHR  ;PUT IT IN THE FILE
       POP     H       ;GET BACK REAL PARM POINTER
       DCR     B       ;COUNTDOWN
       JMP     SUBLP1
;
;       COME HERE WHEN A LINE IS FINISHED,
;       AND WE NEED TO WRITE THE BUFFER TO DISK
;
FLUSH:
       LXI     D,SUBFCB
       MVI     C,WRITEF
       CALL    BDOS
       ORA     A
       JNZ     WRERR   ;CPM RETURNED A WRITE ERROR
       RET
;
;       GETCHR GETS ONE CHAR FROM
;       LINE STORED IN MEMORY
;
GETCHR:
       LXI     H,GETCNT
       MOV     A,M     ;PICK UP COUNT
       DCR     A       ;REMOVE THIS CHAR
       STC             ;PRESET ERROR
       RM              ;RETURN CY IF OUT OF CHARS
       MOV     M,A     ;UPDATE COUNT
       LHLD    GETPTR  ;CURRENT CHAR POINTER
       MOV     A,M     ;PICK UP CHAR
       INX     H       ;BUMP POINTER
       SHLD    GETPTR  ;PUT IT BACK
       CMC             ;TURN CARRY OFF
       RET
;
;       PUTCHR PUTS ONE CHAR TO
;       THE OUTPUT BUFFER
;
PUTCHR:
       LXI     H,PUTCNT
       INR     M       ;INCREMENT COUNT
       JM      LENERR  ;LINE WENT TO > 128 CHARS
       LHLD    PUTPTR  ;GET BUFFER POINTER
       ANI     7FH     ;MASK OUT MSB
       MOV     M,A     ;PUT CHAR THERE
       INX     H       ;BUMP POINTER
       SHLD    PUTPTR  ;PUT IT BACK
       RET             ;ALL DONE
;
;       LOOK AHEAD ONE CHAR IN
;       THE INPUT STREAM.  SET
;       CARRY IF NONE LEFT.
;
LKAHED:
       LDA     GETCNT
       ORA     A       ;SEE IF COUNT IS DOWN TO ZERO
       STC             ;PRE SET INDICATOR
       RZ
       MOV     A,M     ;PICK UP CHAR
       CMC             ;TURN OFF CARRY FLAG
       RET
;
;       LOOK UP PARAMETER WITH NUMBER IN
;       A REG. RETURN A=LENGTH OF PARM,
;       AND HL => PARAMETER
;
LOOKUP:
       CPI     NPAR
       JNC     PAROVF  ;PARM # TOO HIGH
       MOV     L,A
       MVI     H,0     ;NOW HAVE 16 BIT NUMBER
       DAD     H       ;DOUBLE FOR WORD OFFSET
       LXI     D,TABLE
       DAD     D       ;DO THE OFFSET
       MOV     E,M     ;GET ADDRESS OF PARM
       INX     H
       MOV     D,M
       MOV     A,D     ;ANYTHING THERE?
       ORA     E
       JNZ     LKUPOK
       XRA     A       ;NO, ZERO LENGTH
       RET
LKUPOK:
       XCHG            ;NOW IN DE
       MOV     A,M     ;PICK UP LENGTH
       INX     H       ;POINT PAST LENGTH
       RET
;
;       UTILITY COMPARE SUBROUTINE
;
COMPAR:
       LDAX    D
       CMP     M
       RNZ
       INX     H
       INX     D
       DCR     B
       JNZ     COMPAR
       RET
;
;       NUMERIC TEST UTILITY SUBROUTINE
;
NUMTST:
       CPI     '0'
       RC
       CPI     '9'+1
       CMC
       RET
;
;       ERROR HANDLERS
;
WRERR:
       CALL    ERRXIT
       DB      'Disk Full',0
NOMAKE:
       CALL    ERRXIT
       DB      'Dir Full',0
MEMERR:
       CALL    ERRXIT
       DB      'Mem Full',0
NOTFND:
       CALL    ERRXIT
       DB      'SUB File Not Found',0
PARERR:
       CALL    ERRXIT
       DB      'Param',0
PAROVF:
       CALL    ERRXIT
       DB      'Too Many Params',0
LENERR:
       CALL    ERRXIT
       DB      'Line too Long',0
NOTEXT:
       CALL    ERRXIT
       DB      'SUB File Empty',0
CCERR:
       CALL    ERRXIT
       DB      'Ctrl Char',0
ERRXIT:
       CALL    CRLF            ;NEW LINE
       POP     H
       CALL    PSTR            ;PRINT MESSAGE
       CALL    PRINT
       DB      ' Error on Line ',0
       LHLD    LINNUM          ;TELL LINE NUMBER
       CALL    PHLDC
       CALL    CRLF
       LXI     D,SUBFCB        ;DELETE THE $$$.SUB FILE
       MVI     C,DELETF
       CALL    BDOS
       JMP     CPBASE
;
;       INITIALIZE ALL VARIABLES
;
INITVAR:
       LXI     H,VAR
       LXI     B,ENDVAR-VAR
INITLP:
       MVI     M,0     ;ZERO ENTIRE VAR AREA
       INX     H
       DCX     B
       MOV     A,B
       ORA     C
       JNZ     INITLP
       LXI     H,TABLE ;INIT PARM TABLE POINTER
       SHLD    TBLPTR
       LXI     H,0FFFFH        ;MARK END OF TABLE
       SHLD    ENDTBL
       LHLD    FREMEM  ;FREE MEMORY STARTS TXT AREA
       SHLD    TXTPTR
       RET
;
; PRINT HELP WITH PROGRAM OPTIONS
;
HELP:
       CALL    PRINT
       DB      CR,LF,'Syntax:'
       DB      CR,LF,'  SUB                    - Print this HELP Message'
       DB      CR,LF,'  SUB /A <text>          - Abort of SUBMIT File'
       DB      CR,LF,'  SUB /AB <text> - Abort and Ring Bell'
       DB      CR,LF,'  SUB /I<CR>             - Go into Interactive mode'
       DB      CR,LF,'  SUB <FILE> <PARMS>     - Standard SUB File'
       DB      0
       LHLD    SPSAVE  ;RETURN TO OPSYS
       SPHL
       RET
;
;       VARIABLE STORAGE
;
VAR     EQU     $
;
AFLAG:
       DB      0       ;ABORT FLAG (0=NO)
TXTPTR:
       DW      0       ;FREE MEMORY POINTER
TBLPTR:
       DW      0       ;POINTER TO PARM TABLE
DUSER:
       DB      0       ;DEFAULT USER NUMBER
LINNUM:
       DW      0       ;CURRENT LINE NUMBER
PREV:
       DW      0       ;POINTER TO PREV LINE
GETCNT:
       DB      0       ;COUNTER FOR 'GET'
GETPTR:
       DW      0       ;POINTER FOR 'GET'
PUTPTR:
       DW      0       ;POINTER FOR 'PUT'
IBP:
       DB      0       ;INPUT BUFFER POINTER
CLPTR:
       DW      0       ;COMMAND LINE POINTER
BELL$FLAG:
       DB      0       ;RING BELL ON ABORT FLAG
OPTION:
       DB      0       ;OPT OPTION FLAG STORE
TABLE:
       DS      NPAR*3  ;PARAMETER TABLE
ENDTBL:
       DW      0FFFFH  ;END OF PARAMETER TABLE
;
ENDVAR  EQU     $
SPSAVE:
       DW      0       ;STACK POINTER SAVE
;
;
;       FCB FOR $$$.SUB
;
SUBFCB:
       DB      1       ;DRIVE SPECIFIER (A SELECTED)
       DB      '$$$     '
SUBTYP:
       DB      'SUB'
       DW      0,0,0,0 ;INITIALIZE REST OF FCB
       DW      0,0,0,0
       DW      0,0,0,0
;
CLBUF:  DS      2       ;PTR TO COMMAND LINE BUFFER
FREMEM: DS      2       ;PTR TO FREE MEMORY AREA
;
       END