* PROGRAM NAME:  SUB
* AUTHOR:  RICHARD CONN (From SuperSUB Ver 1.1 by Ron Fowler)
* VERSION:  2.3
* DATE:  6 JAN 83
* 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 ZCPR2 VERSION 2.6 AND LATER
VERS    EQU     23

;
;
;************************************************
;*              EXTENDED SUBMIT FOR             *
;*                   CP/M                       *
;************************************************
;
;       SUB is derived from Ron's SuperSUB program; it provides a different
; format for the command line, a command-search hierarchy like CCPZ, a
; resetting of the DMA address, several additional functions, and there are
; several other additions/changes.  Additionally, ZCPR2-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.
;
; REVISED 09/13/81 (RGF): added control character translation
;                         fixed bug in line number reporting
;
;       VERSION 1.1              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
;
; For full details along with examples, see the ac-
; companying documentation file.
;                       --Ron Fowler
;
;
; DEFINE BOOLEANS
;
FALSE   EQU     0
TRUE    EQU     NOT FALSE
;
;************************************************************
;
;               --  User customizable options --
;
CURIND  EQU     '$'     ;CURRENT USER INDICATOR

FORCE$SUB       EQU     FALSE   ;TRUE IF SUBMITTED FILE MUST BE OF TYPE .SUB
TIME$CONST      EQU     0C000H  ;DELAY FOR RINGING BELL
DFLT$USER       EQU     0       ;DEFAULT USER NUMBER TO SEARCH FOR .SUB FILE
DFLT$DISK       EQU     0       ;DEFAULT DISK TO SEARCH FOR .SUB FILE (0=A)
NPAR            EQU     20      ;NUMBER OF ALLOWABLE PARAMETERS
QUIET           EQU     FALSE   ;SET TO TRUE TO ELIMATE SIGN-ON MSG
CPBASE          EQU     0       ;SET TO 4200H FOR HEATH CP/M
OPT             EQU     '/'     ;OPTION DELIMITER CHAR
PDELIM          EQU     '$'     ;PARAMETER DELIMITER
;
;
;
;************************************************************
;
; CP/M DEFINITIONS
;
FGCHAR  EQU     1       ;GET CHAR FUNCTION
FPCHAR  EQU     2       ;PRINT CHAR FUNCTION
DIRIOF  EQU     6       ;DIRECT CONSOLE I/O
PRINTF  EQU     9       ;PRINT STRING FUNCTION
RDBUF   EQU     10      ;READ CONSOLE BUFFER
DETVERS EQU     12      ;GET VERSION NUMBER
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
;
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     3       ;^C
BEL     EQU     7       ;RING BELL
CR      EQU     13      ;CARRIAGE RETURN
LF      EQU     10      ;LINE FEED
TAB     EQU     9

       ORG     TPA
;
;  Branch to Start of Program
;
       JMP     SUBMIT

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;       This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
       DB      0FFH    ; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
       DW      40H     ; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
       DB      0,0     ; DISK, USER FOR FIRST PATH ELEMENT
                       ; DISK = 1 FOR A, '$' FOR CURRENT
                       ; USER = NUMBER, '$' FOR CURRENT
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0     ; DISK, USER FOR 8TH PATH ELEMENT
       DB      0       ; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
       DB      0FFH    ; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
       DW      0FF00H  ; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
       DB      4       ; MAXIMUM NUMBER OF DISKS
MUSER:
       DB      31      ; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
       DB      0FFH    ; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
       DB      0FFH    ; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
       DB      10      ; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
       DB      'chdir',0       ; PASSWORD FOR MOVING INTO PRIV USER AREAS
       DS      41-($-PPASS)    ; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
       DB      '$'     ; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
       DW      80H     ; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
       DW      00000H  ; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
       DB      64      ; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
       DB      'NAMES   '      ; NAME OF DISK NAME FILE
       DB      'DIR'           ; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
       DB      0FFH    ; EXTERNAL PATH?
MCREQD:
       DB      0FFH    ; MULTIPLE COMMAND LINE?
MXREQD:
       DB      0FFH    ; MAX USER/DISK?
UDREQD:
       DB      0FFH    ; ALLOW USER/DISK CHANGE?
PUREQD:
       DB      0FFH    ; PRIVILEGED USER?
CDREQD:
       DB      0FFH    ; CURRENT INDIC AND DMA?
NDREQD:
       DB      0FFH    ; NAMED DIRECTORIES?
Z2CLASS:
       DB      0       ; CLASS 0
       DB      'ZCPR2'
       DS      10      ; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  Start of Program
;
SUBMIT:
       LXI     H,0     ;SAVE STACK IN CASE
       DAD     SP      ;  ONLY HELP REQUESTED
       SHLD    SPSAVE  ;(NOT OTHERWISE USED)
       LXI     SP,STACK
       MVI     C,SETDMA        ;SET DMA ADDRESS
       LXI     D,TBUF  ;SET DMA TO TBUF
       CALL    BDOS
       CALL    START
;
;       SIGN ON MESSAGE
;
       IF      NOT QUIET
       DB      'SUB, Version ',VERS/10+'0','.',(VERS MOD 10)+'0','$'
       ENDIF
;
START:
       POP     D       ;RETRIEVE STRING POINTER
       MVI     C,PRINTF
       CALL    BDOS    ;PRINT THE SIGN-ON
       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     'D'     ;DO COMMAND
       JZ      GPARDO
       CPI     'I'     ;INTERACTIVE MODE
       JZ      GPARINT ;SKIP TO EOL AND RETURN
       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
GPARINT:
       XRA     A       ;TURN OFF COMMAND LINE INPUT
       STA     CLFLAG
       RET
GPARDO:
       INX     H       ;SKIP TO <SP>
       MOV     A,M     ;GET CHAR
       CPI     ' '+1   ;LOOK FOR <SP> OR LESS
       JNC     GPARDO
SLSCAN:
       SHLD    CLPTR   ;SAVE CMD LINE PTR
       MOV     A,M     ;KBD IS SOURCE, GET EOL FLAG
       STA     CLFLAG  ;SAVE AS EOL FLAG
       CPI     ' '     ;ALLOW SPACES AFTER '/'
       RNZ             ;GOT NON-BLANK, DONE
       INX     H       ;ELSE CONTINUE SCAN
       JMP     SLSCAN
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
       LXI     D,ABMSG ;PRINT ABORT MESSAGE
       MVI     C,PRINTF
       CALL    BDOS
       CALL    CHARINB ;GET RESPONSE
       CPI     'A'     ;ABORT?
       JZ      ABORT0  ;RETURN TO CP/M
       CPI     CTRLC   ;ABORT?
       JNZ     ABORT1  ;RETURN TO CP/M
ABORT0:
       LXI     D,SUBFCB        ;DELETE SUBMIT FILE
       MVI     C,DELETF
       CALL    BDOS
       LXI     D,ABMSG1        ;PRINT DONE MESSAGE
       MVI     C,PRINTF
       CALL    BDOS
       JMP     CPBASE  ;RETURN TO CP/M
ABORT1:
       LXI     D,ABMSG2        ;PRINT CONTINUATION MESSAGE
       MVI     C,PRINTF
       CALL    BDOS
       JMP     CPBASE  ; RETURN TO CP/M
ABMSG:
       DB      CR,LF,'Abort SUB File'
       DB      CR,LF,'Do you wish to abort execution?'
       DB      CR,LF,'  Enter A or ^C to Abort or anything else to '
       DB      'continue - $'
ABMSG1:
       DB      'Execution Aborted$'
ABMSG2:
       DB      'Continuing Execution$'
;
;       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:
       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,2             ; OUTPUT TO CON:
       CALL    BDOS
       JMP     CHARINB$LOOP
CHARINB$DONE:
       POP     H               ; RESTORE HL
       JMP     CHARIN1

;
;       INPUT CHAR FROM CON:; CAPITALIZE IT AND ECHO <CRLF>
;
CHARIN:
       MVI     C,FGCHAR        ;GET CHAR
       CALL    BDOS
CHARIN1:
       CALL    UCASE           ;CAPITALIZE
       PUSH    PSW             ;SAVE IT
       CALL    CRLF            ;NEW LINE
       POP     PSW             ;GET IT
       RET
;
;       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
       LXI     D,RDLMSG        ;READING LINE MESSAGE
       MVI     C,PRINTF
       CALL    BDOS
       JMP     LINE
RDFILE1:
       LXI     D,RDFMSG        ;READING FILE MESSAGE
       MVI     C,PRINTF
       CALL    BDOS

*  CHECK FOR .SUB FILE IN CURRENT USER/CURRENT DISK
       LXI     D,FCB   ;WE ARE, OPEN IT
       CALL    INITFCB ;INIT FCB
       LXI     H,INTPATH       ;SET ADDRESS OF PATH
       LDA     EPAVAIL ;EXTERNAL PATHS AVAILABLE?
       ORA     A       ;0=NO
       JZ      OSB1    ;USE INTERNAL PATH
       LHLD    EPADR   ;PT TO EXTERNAL PATH
OSB1:
       CALL    FNDFILE ;SEARCH FOR FILE
       JZ      NOTFND  ;FILE NOT FOUND
       LXI     D,FCB   ;PT TO FCB
       MVI     C,OPENF ;OPEN FILE
       CALL    BDOS
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
       JC      EOF     ;CY SET IF END OF FILE FOUND
       ANI     7FH     ;MASK OUT MSB
       CALL    UCASE   ;CONVERT TO UPPER CASE
       CPI     1AH     ;SEE IF CPM END OF FILE INDICATOR
       JZ      EOF
       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
RDFMSG:
       DB      CR,LF,'Process SUB File$'
RDLMSG:
       DB      CR,LF,'Input SUB File Command Lines$'
;
;       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 MULTIPLE 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 MULTIPLE COMMAND LINE INTO MEMORY BUFFER
;
ZMCL:
       LDA     MCAVAIL ;ANY MULTIPLE COMMANDS?
       ORA     A       ;0=NO
       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
       LHLD    MCADR   ;GET ADDRESS OF MULTIPLE 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    UCASE   ;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
;
Z
MCL2:
       SHLD    TXTPTR  ;SAVE PTR
       POP     H       ;PT TO CHAR COUNT POSITION
       MOV     M,C     ;STORE CHAR COUNT
       RET

*
*  FNDFILE -- LOOK FOR FILE ALONG ZCPR2 PATH
*  INPUT PARAMETERS:  HL = BASE ADDRESS OF PATH, DE = PTR TO FCB OF FILE
*  OUTPUT PARAMETERS:  A=0 AND ZERO FLAG SET IF NOT FOUND, NZ IF FOUND
*
FNDFILE:
       SHLD    PATH            ;SAVE PATH BASE ADDRESS
       MVI     C,17            ;SEARCH FOR FIRST
       CALL    BENTRY          ;LOOK FOR FILE
       INR     A               ;SET FLAG
       JNZ     FF5             ;FOUND IT -- RETURN FOUND FLAG
       XCHG                    ;HL=FCB PTR
       SHLD    FCBPTR          ;SAVE IT
       LHLD    PATH            ;PT TO PATH FOR FAILURE POSSIBILITY
       MVI     C,32            ;GET CURRENT USER
       MVI     E,0FFH
       CALL    BENTRY
       STA     TMPUSR          ;SAVE IT FOR LATER
;
; MAIN SEARCH LOOP
;
FF1:
       MOV     A,M             ;GET DRIVE
       ANI     7FH             ;MASK MSB
       ORA     A               ;0=DONE=COMMAND NOT FOUND
       JNZ     FF2             ;NO ERROR ABORT?
;
; FILE NOT FOUND ERROR
;
       XRA     A               ;ZERO FLAG MEANS NOT FOUND
       RET
;
; LOOK FOR COMMAND IN DIRECTORY PTED TO BY HL; DRIVE IN A
;
FF2:
       CPI     CURIND          ;CURRENT DRIVE SPECIFIED?
       JNZ     FF3             ;SKIP DEFAULT DRIVE SELECTION IF SO
       LDA     UDFLAG          ;GET DEFAULT USER/DISK
       ANI     0FH             ;MASK FOR DEFAULT DISK
       INR     A               ;PREP FOR FOLLOWING DCR A
FF3:
       DCR     A               ;ADJUST PATH 1 TO 0 FOR A, ETC
       MOV     E,A             ;DISK NUMBER IN E
       MVI     C,14            ;SELECT DISK FCT
       CALL    BENTRY          ;SELECT DRIVE
       INX     H               ;PT TO USER NUMBER
       MOV     A,M             ;GET USER NUMBER
       ANI     7FH             ;MASK OUT MSB
       INX     H               ;PT TO NEXT ENTRY IN PATH
       PUSH    H               ;SAVE PTR
       CPI     CURIND          ;CURRENT USER SPECIFIED?
       JNZ     FF4             ;DO NOT SELECT CURRENT USER IF SO
       LDA     TMPUSR          ;GET ORIGINAL USER NUMBER
FF4:
       MOV     E,A             ;SELECT USER
       MVI     C,32
       CALL    BENTRY
       LHLD    FCBPTR          ;GET PTR TO FCB
       XCHG                    ;... IN DE
       MVI     C,17            ;SEARCH FOR FIRST
       CALL    BENTRY          ;LOOK FOR FILE
       POP     H               ;GET PTR TO NEXT PATH ENTRY
       INR     A               ;SET FLAG
       JZ      FF1             ;CONTINUE PATH SEARCH IF SEARCH FAILED
;
; FILE FOUND -- PERFORM SYSTEM TEST AND PROCEED IF APPROVED
;
FF5:
       MVI     A,0FFH          ;SET OK RETURN
       ORA     A
       RET

;
;  BDOS ROUTINE
;
BENTRY:
       PUSH    H       ;SAVE REGS
       PUSH    D
       PUSH    B
       CALL    BDOS
       POP     B       ;GET REGS
       POP     D
       POP     H
       RET

* BUFFERS
FCBPTR:
       DS      2       ;POINTER TO FCB FOR FILE SEARCH
TMPUSR:
       DS      1       ;CURRENT USER NUMBER
PATH:
       DS      2       ;BASE ADDRESS OF PATH
;
;       INITIALIZE KEY FIELDS OF FCB PTED TO BY DE
;
INITFCB:
       PUSH    H       ;SAVE HL
       XRA     A       ;A=0
       STAX    D       ;SET DEFAULT DRIVE
       LXI     H,FCBEX ;PT TO EX FIELD
       DAD     D
       MOV     M,A     ;SET EX FIELD TO ZERO
       LXI     H,FCBNR ;PT TO CR FIELD
       DAD     D
       MOV     M,A     ;SET CR FIELD TO ZERO
       POP     H       ;RESTORE HL
       RET
;
;       GET NEXT BYTE FROM INPUT FILE
;
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     NSLASH  ;JUMP IF WE ARE
       CALL    GNBKBD  ;NO, GET A BYTE FROM KBD INPUT
       JMP     GNBXIT  ;THEN LEAVE
NSLASH:
       LDA     IBP     ;GET BUFFER POINTER
       ORA     A       ;PAST END?
       CM      FILL    ;WRAPPED AROUND
       JNC     GNB1    ;NO END OF FILE

*  RESTORE CURRENT USER/DISK IF CHANGED
       LDA     UDFLAG  ;RESTORE CURRENT DISK
       ANI     0FH     ;MASK IN DISK ONLY
       MOV     E,A
       MVI     C,LOGIN ;BDOS FCT
       CALL    BDOS
       LDA     DUSER   ;RESTORE CURRENT USER
       MOV     E,A
       MVI     C,UCODE ;BDOS FCT
       CALL    BDOS

       MVI     A,1AH   ;FAKE EOF
GNB1:
       MOV     E,A     ;PUT IN DE
       MVI     D,0
       INR     A       ;POINT TO NEXT
       STA     IBP     ;PUT AWAY
       LXI     H,TBUF  ;NOW OFFSET INTO BUFR
       DAD     D
       MOV     A,M     ;GET CHAR THERE
GNBXIT:
       POP     B       ;RESTORE EVERYBODY
       POP     D
       POP     H
       ORA     A       ;TURN ON CARRY
       RET
;
;       FILL INPUT BUFFER
;
FILL:
       MVI     C,READF
       LXI     D,FCB
       CALL    BDOS
       ORA     A       ;GOT GOOD READ?
       MVI     A,0     ;(NEW BUF PTR)
       STC
       RNZ             ;RETN CY=EOF
       CMC             ;NO EOF, NO CY
       RET
;
; COME HERE TO GET A .SUB CHARACTER WHEN
; WE'RE NOT USING A .SUB FILE ("/" OPTION)
;
GNBKBD:
       LDA     CLFLAG  ;USE CP/M CMD LINE?
       ORA     A
       JNZ     GNBCL   ;THEN GO DO IT
       LDA     CLCNT   ;NOT, CHECK LOCAL
       ORA     A       ;  CMD LINE CHAR COUNT
       CM      CLFILL  ;REFILL WHEN IT WRAPS BACK
       JC      GKEND   ;GOT CARRY (FROM CLFILL), RETURN EOF
       DCR     A       ;COUNT DOWN
       STA     CLCNT
       JP      GNBCL   ;IF PLUS, BUFFER NOT EMPTY
       MVI     A,CR    ;OUT OF CHARS, RETURN A CR
       RET
GKEND:
       MVI     A,1AH   ;RETURN EOF
       RET
;
; GET NEXT BYTE OF INPUT FROM CMD LINE @CLPTR
;
GNBCL:
       LHLD    CLPTR   ;LOAD THE POINTER
       MOV     A,M     ;GET THE CHAR
       INX     H       ;BUMP POINTER FOR NEXT TIME
       SHLD    CLPTR
       ORA     A       ;PHYSICAL END-OF-LINE
       RNZ             ;THIS ONLY NEEDED WHEN INPUT
                       ;  SOURCE IS ORIG CPM CMD LINE
       MVI     A,1AH   ;TRANSLATE THAT TO END OF FILE
       RET
;
; SUBROUTINE TO RE-FILL THE LOCAL COMMAND LINE
;
CLFILL:
       LXI     D,PROMPT        ;PRINT A PROMPT
       MVI     C,PRINTF        ;USE CP/M FUNCT 9
       CALL    BDOS
       LXI     D,CLBUF ;NOW FILL THE BUFFER
       MVI     C,RDBUF
       CALL    BDOS
       LDA     CLCNT   ;RETURN WITH COUNT IN A
       LXI     H,CLTEXT        ;RESET THE CMD LINE POINTER
       SHLD    CLPTR
       ORA     A       ;SET CY ON LEN NZ
       STC
       RZ
       CMC
       RET
;
;       MAKE SURE NO MEMORY OVERFLOW
;
SIZE:
       LDA     BDOS+2  ;HIGHEST PAGE POINTER
       DCR     A       ;MAKE IT BE UNDER BDOS
       CMP     H       ;CHECK IT AGAINST CURRENT PAGE
       RNC             ;NC=ALL OKAY
       JMP     MEMERR  ;OTHERWISE ABORT
;
;       SET UP THE $$$.SUB FILE
;       FOR WRITING
;
WRSET:
       LXI     D,WRSUBMSG
       MVI     C,PRINTF
       CALL    BDOS
       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
;
WRSUBMSG:
       DB      CR,LF,'Writing SUB Execution File to Disk$'
;
;       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
;
;DECIMAL OUTPUT ROUTINE
;
DECOUT:
       PUSH    B
       PUSH    D
       PUSH    H
       LXI     B,-10
       LXI     D,-1
;
DECOU2:
       DAD     B
       INX     D
       JC      DECOU2
       LXI     B,10
       DAD     B
       XCHG
       MOV     A,H
       ORA     L
       CNZ     DECOUT
       MOV     A,E
       ADI     '0'
       CALL    TYPE
       POP     H
       POP     D
       POP     B
       RET
;
; PRINT CR, LF ON CONSOLE
;
CRLF:
       MVI     A,CR
       CALL    TYPE
       MVI     A,LF
;
; PRINT CHAR IN A ON CONSOLE
;
TYPE:
       PUSH    H       ;SAVE REGS
       PUSH    D
       PUSH    B
       MOV     E,A     ;PUT IN E FOR CP/M
       MVI     C,FPCHAR
       CALL    BDOS    ;PRINT IT
       POP     B       ;RESTORE ALL
       POP     D
       POP     H
       RET
;
; CONVERT CHAR IN A TO UPPER CASE
;
UCASE:
       CPI     'a'     ;VALIDATE CASE
       RC
       CPI     'z'+1
       RNC
       ANI     5FH     ;GOT LC, CONV TO UC
       RET
;
;       ERROR HANDLERS
;
WRERR:
       CALL    ERRXIT
       DB      'Disk Full$'
NOMAKE:
       CALL    ERRXIT
       DB      'Directory Full$'
MEMERR:
       CALL    ERRXIT
       DB      'Memory Full$'
NOTFND:
       CALL    ERRXIT
       DB      'SUBMIT File Not Found$'
PARERR:
       CALL    ERRXIT
       DB      'Parameter$'
PAROVF:
       CALL    ERRXIT
       DB      'Too Many Parameters: $'
LENERR:
       CALL    ERRXIT
       DB      'Line too Long: $'
NOTEXT:
       CALL    ERRXIT
       DB      'SUBMIT File Empty$'
CCERR:
       CALL    ERRXIT
       DB      'Control Character$'
ERRXIT:
       LXI     D,SUBERR        ;PRINT ERROR HERALD
       MVI     C,PRINTF
       CALL    BDOS
       POP     D
       MVI     C,PRINTF
       CALL    BDOS
       LXI     D,ERRMSG        ;PRINT 2ND HALF MSG
       MVI     C,PRINTF
       CALL    BDOS
       LHLD    LINNUM          ;TELL LINE NUMBER
       CALL    DECOUT
       CALL    CRLF
       LXI     D,SUBFCB        ;DELETE THE $$$.SUB FILE
       MVI     C,DELETF
       CALL    BDOS
       JMP     CPBASE
;
SUBERR:
       DB      CR,LF,'SUB Error -- $'
ERRMSG:
       DB      ' error on line number: $'
;
; PROMPT FOR COMMAND LINE INPUT
;
PROMPT:
       DB      CR,LF,'Command Line? $'
;
;       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
       LXI     H,FREMEM        ;FREE MEMORY STARTS TXT AREA
       SHLD    TXTPTR
       MVI     A,80H   ;FORCE READ
       STA     IBP
       STA     CLCNT   ;FORCE CONSOLE READ
;  GET CURRENT USER NUMBER FOR LATER
       MVI     E,0FFH  ;GET USER CODE
       MVI     C,UCODE ;BDOS FCT
       CALL    BDOS
       STA     DUSER   ;DEFAULT USER
       RET
;
; PRINT HELP WITH PROGRAM OPTIONS
;
HELP:
       LXI     D,HLPMSG        ;PRINT THE HELP STUFF
       MVI     C,PRINTF
       CALL    BDOS
       LHLD    SPSAVE  ;THEN RETURN W/NO WARM-BOOT
       SPHL
       RET
;
HLPMSG:
       DB      CR,LF,'How to use SUB --',CR,LF
       DB      CR,LF,'SUB<CR>                  - print this HELP message'
       DB      CR,LF,'SUB /A <text>            - Abort of SUBMIT File'
       DB      CR,LF,'SUB /AB <text>           - /A and Ring Bell'
       DB      CR,LF,'SUB /D <cmd lines>       - use SUMMARY (DO) mode'
       DB      CR,LF,'SUB /I<CR>               - go into Interactive mode'
       DB      CR,LF,'SUB <FILE> <PARMS>       - as in standard SUBMIT.COM'
       DB      CR,LF
       DB      CR,LF,'In "/I" (interactive) mode, SUB will prompt you'
       DB      CR,LF,'a line at a time for the SUBMIT job input...logical'
       DB      CR,LF,'lines may be combined on the same input line by sep-'
       DB      CR,LF,'erating them with semicolons.  Example:'
       DB      CR,LF,'  A>SUB /D STAT;DIR'
       DB      CR,LF,'specifies two commands on the same input line.',CR,LF
       DB      CR,LF,'Submitted jobs may be nested...SUB does not erase'
       DB      CR,LF,'any existing submit job (appends to them instead).'
       DB      CR,LF
       DB      CR,LF,'To insert a control character into the output, pre-'
       DB      CR,LF,'fix it with a "^" (works in any mode).'
       DB      CR,LF,'$'
;
;       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
CLFLAG:
       DB      0       ;USE CP/M CMD LINE FLAG
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     $
;
; COMMAND LINE BUFFER...NOT INITIALIZED
;
CLBUF:
       DB      128     ;BUFFER LENGTH
CLCNT:
       DB      0       ;CHARACTER COUNTER
CLTEXT:
       DS      128     ;THE BUFFER ITSELF
;
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
;
;       STACK AREA
;
       DS      200     ;WHY NOT?
STACK   EQU     $
FREMEM  EQU     $
;
       END     SUB