; ;******************************************************** ;* Extended Submit for * ;* CP/M * ;******************************************************** ; ; 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, MI ; ; Published in Lifelines, Volume II, Number 8- Jan 82 ; ; ; This program is intended as a replacement for the ; SUBMIT program provided with CP/M. It provides several ; 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 --- ; npar equ 10 ; Number of Allowable Parameters subdrv equ 0 ; Make 0 for dflt - 1,2,3 for A,B,C quiet equ false ; Set to true to eliminate sign-on msg cpbase equ 0 ; Set to 4200h for Heath CP/M ; ; ; ;*************************************************************** ; ; CP/M Definitions ; fpchar equ 2 ; Print char function printf equ 9 ; Print string function rdbuf equ 10 ; Read console buffer 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 ; bdos equ cpbase+5 ; fcb equ 5ch ; Default file control block 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 ; cr equ 13 ; Carriage return lf equ 10 ; Line feed tab equ 9 ; Tab Char ; ; Start of Program Code ; org tpa ; ; Get the ball rolling ; submit: lxi h,0 ; Save stack in case dad sp ; only help requested shld spsave ; (Not otherwise used) lxi sp,stack call start ; ; Sign on Message ; if not quiet db 'SuperSUB V1.1' endif ; db cr,lf,'$' ; Newline even if quiet ; start: pop d ; Retrieve string pointer mvi c,printf lda fcb+1 ; Anything on CMd line? cpi ' ' jz help ; No, Go print help call bdos ; Print the sign-on call initvar ; Initialize the variable area call getpar ; Get command line parameters 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 the the .SUB text file ; setup: lxi h,fcb+ft ; Look at 1st char of mov a,m ; file type. If it is cpi ' ' ; blank, then go move jz putsub ; "SUB" into FT field lxi d,subtyp; It's not blank to make mvi b,3 ; sure it's already call compar ; "SUB" jnz notfnd ; If not, it's an error ret ; ; Move "SUB" into the file type ; putsub: xchg lxi h,subtyp ; By convention, move mvi b,3 ; @HL to @DE 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 ; ret ; ; 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 specified in the command ; line. ; getpar: 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 '/' ; No, check option jnz glpo ; Not keyboard input, read file inx h ; point past '/' 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 ; Not non-blank - done inx h ; Else continue scan jmp slscan glpo: 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 glpo glp: call scanto ; Pass up the blanks rc ; CY returned if end of cmd line call putpar ; Not 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 is found, returns carry set. ; scanto: mov a,m inx h ora a ; Set flags on zero stc ; In case zero found rz cpi ' ' jz scanto ; Scan past blanks cpi tab ; Do tabs to, 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 pree area of table mov a,m ; Non-zero in table ora a ; indicates table jnz parovf ; overflow 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 is zero (EOL Mark) cmc 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 '/' ; Option tells jz line ; Jump if not lxi d,fcb ; We are, open it. mvi c,openf call bdos inr a ; if 0FFh returned, jz notfnd ; then file not found. line: lhld linnum ; Bump line number inx h shld linnum lhld prev ; Get prev line pointer xchg lhld txtptr ; Get current free mem pointer shld prev ; Make it the prev line (for next 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 call ucase ; Convert to upper case cpi 1ah ; See if CP/M End of File 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 to 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 pop h ; Current line's length pointer mov m,c ; Store length away ret ; All done reading SUB file ; ; Get next byte from Input File ; gnb: push h ; Don't alter anybody push d push b lda option ; Input from .SUB file? cpi '/' ; 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 mvi 1,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 buff 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 mvi a,0 stc rnz ; Rtn 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 cpi ';' ; Logical end-of-line? jnz nsemi ; Jump if not mvi a,cr ; Yes, translate it ret nsemi: 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 function 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,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 that next rec 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 ; nul 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 '$' ; Parameter specifier? jnz stobyt ; If not, just write char lda option ; Check option: '$' doesn't cpi '/' ; count in '/' mode mvi a,'$' ; (Restore the '$') jz stobyt call lkahed ; Peek at next char jc parerr ; Line ending means param err cpi '$' ; 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 numric jc dosubs ; Done in 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 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 ; Preset 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 the 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 push d push b mov e,a ; Put in E for CP/M mvi c,fpchar call bdos ; Print it pop b 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: pop d mvi c,printf call bdos lxi d,errmsg ; Print the 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 ; errmsg: db ' error on line number: $' ; ; Prompt for command line input prompt: db cr,lf,'*$' ; ; 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 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 SUPERSUB:',cr,lf db cr,lf,'SUPERSUB :print this HELP message' db cr,lf,'SUPERSUB / :go into interactive mode' db cr,lf,'SUPERSUB / :use SUMMARY mode' db cr,lf,'SUPERSUB :as in standard SUBMIT.COM' db cr,lf db cr,lf,'IN "/" (interactive) mode, SUPERSUB 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>SUPERSUB /STAT;DIR' db cr,lf,'specifies two commands on the same input line.',cr,lf db cr,lf,'Submitted jobs may be nested...SUPERSUB 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 $ ; txtptr: dw 0 ; Free memory pointer tblptr: dw 0 ; Pointer to Parm Table linnum: dw 0 ; Current line number prev: dw 0 ; Pointer to previous 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 option: db 0 ; '/' option flag store table: ds npar*3 ; Parameter table endtbl: dw 0 ; 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 subdrv ; Driver Specifier db '$$$ ' subtyp: db 'SUB' dw 0,0,0,0 ; Initialize rest of FCB dw 0,0,0,0 dw 0,0 ; ; Stack Area ; ds 200 stack equ $ fremem equ $ ; end submit