;
;********************************************************
;*		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<CR>		:print this HELP message'
	db	cr,lf,'SUPERSUB /<CR>		:go into interactive mode'
	db	cr,lf,'SUPERSUB /<cmd lines>	:use SUMMARY mode'
	db	cr,lf,'SUPERSUB <FILE> <PARMS>	: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
