title	'console command processor (CCP), ver 2.0'
;	assembly language version of the CP/M console command processor
;
;	version 2.2 February, 1980
;
;	Copyright (c) 1976, 1977, 1978, 1979, 1980
;	Digital Research
;	Box 579, Pacific Grove,
;	California, 93950
;
false	equ	0000h
true	equ	not false
testing	equ	false	;true if debugging
;
;
	if	testing
	org	3400h
bdosl	equ	$+800h		;bdos location
	else
	org	000h
bdosl	equ	$+800h		;bdos location
	endif
tran	equ	100h
tranm	equ	$
ccploc	equ	$
;
;	********************************************************
;	*	Base of CCP contains the following code/data   *
;	*	ccp:	jmp ccpstart	(start with command)   *
;	*		jmp ccpclear    (start, clear command) *
;	*	ccp+6	127		(max command length)   *
;	*	ccp+7	comlen		(command length = 00)  *
;	*	ccp+8	' ... '		(16 blanks)	       *
;	********************************************************
;	* Normal entry is at ccp, where the command line given *
;	* at ccp+8 is executed automatically (normally a null  *
;	* command with comlen = 00).  An initializing program  *
;	* can be automatically loaded by storing the command   *
;	* at ccp+8, with the command length at ccp+7.  In this *
;	* case, the ccp executes the command before prompting  *
;	* the console for input.  Note that the command is exe-*
;	* cuted on both warm and cold starts.  When the command*
;	* line is initialized, a jump to "jmp ccpclear" dis-   *
;	* ables the automatic command execution.               *
;	********************************************************
;
	jmp	ccpstart	;start ccp with possible initial command
	jmp	ccpclear	;clear the command buffer
maxlen:	db	127	;max buffer length
comlen:	db	0	;command length (filled in by dos)
;	(command executed initially if comlen non zero)
combuf:
	db	'        '	;8 character fill
	db	'        '	;8 character fill
	db	'COPYRIGHT (C) 1979, DIGITAL RESEARCH  '; 38
	ds	128-($-combuf)
;	total buffer length is 128 characters
comaddr:dw	combuf	;address of next to char to scan
staddr:	ds	2	;starting address of current fillfcb request
;
diska	equ	0004h	;disk address for current disk
bdos	equ	0005h	;primary bdos entry point
buff	equ	0080h	;default buffer
fcb	equ	005ch	;default file control block
;
rcharf	equ	1	;read character function
pcharf	equ	2	;print character function
pbuff	equ	9	;print buffer function
rbuff	equ	10	;read buffer function
breakf	equ	11	;break key function
liftf	equ	12	;lift head function (no operation)
initf	equ	13	;initialize bdos function
self	equ	14	;select disk function
openf	equ	15	;open file function
closef	equ	16	;close file function
searf	equ	17	;search for file function
searnf	equ	18	;search for next file function
delf	equ	19	;delete file function
dreadf	equ	20	;disk read function
dwritf	equ	21	;disk write function
makef	equ	22	;file make function
renf	equ	23	;rename file function
logf	equ	24	;return login vector
cself	equ	25	;return currently selected drive number
dmaf	equ	26	;set dma address
userf	equ	32	;set user number
;
;	special fcb flags
rofile	equ	9	;read only file
sysfile	equ	10	;system file flag
;
;	special characters
cr	equ	13	;carriage return
lf	equ	10	;line feed
la	equ	5fh	;left arrow
eofile	equ	1ah	;end of file
;
;	utility procedures
printchar:
	mov e,a! mvi c,pcharf! jmp bdos
;
printbc:
	;print character, but save b,c registers
	push b! call printchar! pop b! ret
;
crlf:
	mvi a,cr! call printbc
	mvi a,lf! jmp  printbc
;
blank:
	mvi a,' '! jmp printbc
;
print:	;print string starting at b,c until next 00 entry
	push b! call crlf! pop h ;now print the string
prin0:	mov a,m! ora a! rz ;stop on 00
		inx h! push h ;ready for next
		call printchar! pop h ;character printed
		jmp prin0 ;for another character
;
initialize:
	mvi c,initf! jmp bdos
;
select:
	mov e,a! mvi c,self! jmp bdos
;
bdos$inr:
	call bdos! sta dcnt! inr a! ret
;
open:	;open the file given by d,e
	mvi c,openf! jmp bdos$inr
;
openc:	;open comfcb
	xra a! sta comrec ;clear next record to read
	lxi d,comfcb! jmp open
;
close:	;close the file given by d,e
	mvi c,closef! jmp bdos$inr
;
search:	;search for the file given by d,e
	mvi c,searf! jmp bdos$inr
;
searchn:
	;search for the next occurrence of the file given by d,e
	mvi c,searnf! jmp bdos$inr
;
searchcom:
	;search for comfcb file
	lxi d,comfcb! jmp search
;
delete:	;delete the file given by d,e
	mvi c,delf! jmp bdos
;
bdos$cond:
	call bdos! ora a! ret
;
diskread:
	;read the next record from the file given by d,e
	mvi c,dreadf! jmp bdos$cond
;
diskreadc:
	;read the comfcb file
	lxi d,comfcb! jmp diskread
;
diskwrite:
	;write the next record to the file given by d,e
	mvi c,dwritf! jmp bdos$cond
;
make:	;create the file given by d,e
	mvi c,makef! jmp bdos$inr
;
renam:	;rename the file given by d,e
	mvi c,renf! jmp bdos
;
getuser:
	;return current user code in a
	mvi e,0ffh ;drop through to setuser
;
setuser:
        mvi c,userf! jmp bdos ;sets user number
;
saveuser:
	;save user#/disk# before possible ^c or transient
	call getuser ;code to a
	add a! add a! add a! add a ;rot left
	lxi h,cdisk! ora m ;4b=user, 4b=disk
	sta diska ;stored away in memory for later
	ret
;
setdiska:
	lda cdisk! sta diska ;user/disk
	ret
;
translate:
	;translate character in register A to upper case
	cpi 61h! rc ;return if below lower case a
	cpi 7bh! rnc ;return if above lower case z
	ani 5fh! ret ;translated to upper case
;
readcom:
	;read the next command into the command buffer
	;check for submit file
	lda submit! ora a! jz nosub
		;scanning a submit file
		;change drives to open and read the file
		lda cdisk! ora a! mvi a,0! cnz select
		;have to open again in case xsub present
                lxi d,subfcb! call open! jz nosub ;skip if no sub
		lda subrc! dcr a ;read last record(s) first
		sta subcr ;current record to read
		lxi d,subfcb! call diskread ;end of file if last record
		jnz nosub
			;disk read is ok, transfer to combuf
			lxi d,comlen! lxi h,buff! mvi b,128! call move0
			;line is transferred, close the file with a
			;deleted record
			lxi h,submod! mvi m,0 ;clear fwflag
			inx h! dcr m ;one less record
			lxi d,subfcb! call close! jz nosub
			;close went ok, return to original drive
			lda cdisk! ora a! cnz select
			;print to the 00
			lxi h,combuf! call prin0
			call break$key! jz noread
			call del$sub! jmp ccp ;break key depressed
			;
	nosub:	;no submit file! call del$sub
	;translate to upper case, store zero at end
	call saveuser ;user # save in case control c
	mvi c,rbuff! lxi d,maxlen! call bdos
	call setdiska ;no control c, so restore diska
	noread:	;enter here from submit file
	;set the last character to zero for later scans
	lxi h,comlen! mov b,m ;length is in b
	readcom0: inx h! mov a,b! ora a ;end of scan?
		jz readcom1! mov a,m ;get character and translate
		call translate! mov m,a! dcr b! jmp readcom0
		;
	readcom1: ;end of scan, h,l address end of command
		mov m,a ;store a zero
		lxi h,combuf! shld comaddr ;ready to scan to zero
	ret
;
break$key:
	;check for a character ready at the console
	mvi c,breakf! call bdos
	ora a! rz
	mvi c,rcharf! call bdos ;character cleared
	ora a! ret
;
cselect:
	;get the currently selected drive number to reg-A
	mvi c,cself! jmp bdos
;
setdmabuff:
	;set default buffer dma address
	lxi d,buff ;(drop through)
;
setdma:
	;set dma address to d,e
	mvi c,dmaf! jmp bdos
;
del$sub:
	;delete the submit file, and set submit flag to false
	lxi h,submit! mov a,m! ora a! rz ;return if no sub file
	mvi m,0 ;submit flag is set to false
	xra a! call select ;on drive a to erase file
	lxi d,subfcb! call delete
	lda cdisk! jmp select ;back to original drive
;
serialize:
	;check serialization
	lxi d,serial! lxi h,bdosl! mvi b,6 ;check six bytes
	ser0:	ldax d! cmp m! jnz badserial
		inx d! inx h! dcr b! jnz ser0
		ret ;serial number is ok
;
comerr:
	;error in command string starting at position
	;'staddr' and ending with first delimiter
	call crlf ;space to next line
	lhld staddr ;h,l address first to print
	comerr0: ;print characters until blank or zero
		mov a,m! cpi ' '! jz comerr1; not blank
		ora a! jz comerr1; not zero, so print it
		push h! call printchar! pop h! inx h
		jmp comerr0; for another character
	comerr1: ;print question mark,and delete sub file
		mvi a,'?'! call printchar
		call crlf! call del$sub
		jmp ccp ;restart with next command
;
; fcb scan and fill subroutine (entry is at fillfcb below)
	;fill the comfcb, indexed by A (0 or 16)
	;subroutines
	delim:	;look for a delimiter
		ldax d! ora a! rz ;not the last element
		cpi ' '! jc comerr ;non graphic
		rz ;treat blank as delimiter
		cpi '='! rz
		cpi la!  rz ;left arrow
		cpi '.'! rz
		cpi ':'! rz
		cpi ';'! rz
		cpi '<'! rz
		cpi '>'! rz
		ret	;delimiter not found
;
	deblank: ;deblank the input line
		ldax d! ora a! rz ;treat end of line as blank
		cpi ' '! rnz! inx d! jmp deblank
;
	addh: ;add a to h,l
		add l! mov l,a! rnc
		inr h! ret
		;
fillfcb0:
	;equivalent to fillfcb(0)
	mvi a,0
;
fillfcb:
	lxi h,comfcb! call addh! push h! push h ;fcb rescanned at end
	xra a! sta sdisk ;clear selected disk (in case A:...)
	lhld comaddr! xchg ;command address in d,e
	call deblank ;to first non-blank character
	xchg! shld staddr ;in case of errors
	xchg! pop h ;d,e has command, h,l has fcb address
	;look for preceding file name A: B: ...
	ldax d! ora a! jz setcur0 ;use current disk if empty command
	sbi 'A'-1! mov b,a ;disk name held in b if : follows
	inx d! ldax d! cpi ':'! jz setdsk ;set disk name if :
	;
	setcur: ;set current disk
		dcx d ;back to first character of command
	setcur0:
		lda cdisk! mov m,a! jmp setname
	;
	setdsk: ;set disk to name in register b
		mov a,b! sta sdisk ;mark as disk selected
		mov m,b! inx d ;past the :
	;
	setname: ;set the file name field
		mvi b,8 ;file name length (max)
		setnam0: call delim! jz padname ;not a delimiter
			inx h! cpi '*'! jnz setnam1 ;must be ?'s
			mvi m,'?'! jmp setnam2 ;to dec count
		;
		setnam1: mov m,a ;store character to fcb! inx d
		setnam2: dcr b ;count down length! jnz setnam0
		;
	;end of name, truncate remainder
	trname: call delim! jz setty ;set type field if delimiter
		inx d! jmp trname
		;
	padname: inx h! mvi m,' '! dcr b! jnz padname
		;
	setty: ;set the type field
		mvi b,3! cpi '.'! jnz padty ;skip the type field if no .
		inx d ;past the ., to the file type field
		setty0: ;set the field from the command buffer
			call delim! jz padty! inx h! cpi '*'! jnz setty1
			mvi m,'?' ;since * specified! jmp setty2
			;
		setty1: ;not a *, so copy to type field
			mov m,a! inx d
		setty2: ;decrement count and go again
			dcr b! jnz setty0
			;
		;end of type field, truncate
	trtyp: ;truncate type field
		call delim! jz efill! inx d! jmp trtyp
		;
		padty:	;pad the type field with blanks
			inx h! mvi m,' '! dcr b! jnz padty
		;
	efill: ;end of the filename/filetype fill, save command address
		;fill the remaining fields for the fcb
		mvi b,3
		efill0: inx h! mvi m,0! dcr b! jnz efill0
		xchg! shld comaddr ;set new starting point
		;
		;recover the start address of the fcb and count ?'s
		pop h! lxi b,11 ;b=0, c=8+3
		scnq: inx h! mov a,m! cpi '?'! jnz scnq0
		;? found, count it in b! inr b
		scnq0: dcr c! jnz scnq
		;
		;number of ?'s in c, move to a and return with flags set
		mov a,b! ora a! ret
;
intvec:
	;intrinsic function names (all are four characters)
	db	'DIR '
	db	'ERA '
	db	'TYPE'
	db	'SAVE'
	db	'REN '
        db      'USER'
	intlen equ ($-intvec)/4 ;intrinsic function length
	serial: db 0,0,0,0,0,0
;
;
intrinsic:
	;look for intrinsic functions (comfcb has been filled)
	lxi h,intvec! mvi c,0 ;c counts intrinsics as scanned
	intrin0: mov a,c! cpi intlen ;done with scan?! rnc
		;no, more to scan
		lxi d,comfcb+1 ;beginning of name
		mvi b,4 ;length of match is in b
		intrin1: ldax d! cmp m ;match?
			jnz intrin2 ;skip if no match
			inx d! inx h! dcr b
			jnz intrin1 ;loop while matching
		;
		;complete match on name, check for blank in fcb
		ldax d! cpi ' '! jnz intrin3 ;otherwise matched
		mov a,c! ret ;with intrinsic number in a
		;
		intrin2: ;mismatch, move to end of intrinsic
			inx h! dcr b! jnz intrin2
		;
		intrin3: ;try next intrinsic
			inr c ;to next intrinsic number
			jmp intrin0 ;for another round
;
ccpclear:
	;clear the command buffer
	xra	a
	sta	comlen
	;drop through to start ccp
ccpstart:
	;enter here from boot loader
	lxi sp,stack! push b ;save initial disk number
        ;(high order 4bits=user code, low 4bits=disk#)
	mov a,c! rar! rar! rar! rar! ani 0fh ;user code
	mov e,a! call setuser ;user code selected
	;initialize for this user, get $ flag
        call initialize ;0ffh in accum if $ file present
        sta submit ;submit flag set if $ file present
        pop b ;recall user code and disk number
	mov a,c! ani 0fh ;disk number in accumulator
        sta cdisk ;clears user code nibble
	call select ;proper disk is selected, now check sub files
	;check for initial command
	lda comlen! ora a! jnz ccp0	;assume typed already
;
ccp:
	;enter here on each command or error condition
	lxi sp,stack
	call crlf ;print d> prompt, where d is disk name
	call cselect ;get current disk number
	adi 'A'! call printchar
	mvi a,'>'! call printchar
	call readcom ;command buffer filled
ccp0:	;(enter here from initialization with command full)
	lxi d,buff! call setdma ;default dma address at buff
	call cselect! sta cdisk ;current disk number saved
	call fillfcb0 ;command fcb filled
	cnz comerr ;the name cannot be an ambiguous reference
	lda sdisk! ora a! jnz userfunc
		;check for an intrinsic function
		call intrinsic
		lxi h,jmptab ;index is in the accumulator
		mov e,a! mvi d,0! dad d! dad d ;index in d,e
		mov a,m! inx h! mov h,m! mov l,a! pchl
		;pc changes to the proper intrinsic or user function
		jmptab:
			dw	direct	;directory search
			dw	erase	;file erase
			dw	type	;type file
			dw	save	;save memory image
			dw	rename	;file rename
			dw	user	;user number
			dw	userfunc;user-defined function
		badserial:
			lxi h,di or (hlt shl 8)
			shld ccploc! lxi h,ccploc! pchl
			;
;
	;utility subroutines for intrinsic handlers
	readerr:
		;print the read error message
		lxi b,rdmsg! jmp print
		rdmsg: db 'READ ERROR',0
	;
	nofile:
		;print no file message
		lxi b,nofmsg! jmp print
		nofmsg: db 'NO FILE',0
	;
	getnumber: ;read a number from the command line
		call fillfcb0 ;should be number
		lda sdisk! ora a! jnz comerr ;cannot be prefixed
		;convert the byte value in comfcb to binary
		lxi h,comfcb+1! lxi b,11 ;(b=0, c=11)
		;value accumulated in b, c counts name length to zero
		conv0:	mov a,m! cpi ' '! jz conv1
			;more to scan, convert char to binary and add
			inx h! sui '0'! cpi 10! jnc comerr ;valid?
			mov d,a ;save value! mov a,b ;mult by 10
			ani 1110$0000b! jnz comerr
			mov a,b ;recover value
			rlc! rlc! rlc ;*8
			add b! jc comerr
			add b! jc comerr ;*8+*2 = *10
			add d! jc comerr ;+digit
			mov b,a! dcr c! jnz conv0 ;for another digit
			ret
		conv1:	;end of digits, check for all blanks
			mov a,m! cpi ' '! jnz comerr ;blanks?
			inx h! dcr c! jnz conv1
			mov a,b ;recover value! ret
		;
	movename:
		;move 3 characters from h,l to d,e addresses
		mvi b,3
		move0: mov a,m! stax d! inx h! inx d
			dcr b! jnz move0
		ret
	;
	addhcf:	;buff + a + c to h,l followed by fetch
		lxi h,buff! add c! call addh! mov a,m! ret
	;
	setdisk:
		;change disks for this command, if requested
		xra a! sta comfcb ;clear disk name from fcb
		lda sdisk! ora a! rz ;no action if not specified
		dcr a! lxi h,cdisk! cmp m! rz ;already selected
		jmp select
	;
	resetdisk:
		;return to original disk after command
		lda sdisk! ora a! rz ;no action if not selected
		dcr a! lxi h,cdisk! cmp m! rz ;same disk
		lda cdisk! jmp select
;
	;individual intrinsics follow
direct:
	;directory search
	call fillfcb0 ;comfcb gets file name
	call setdisk ;change disk drives if requested
	lxi h,comfcb+1! mov a,m ;may be empty request
	cpi ' '! jnz dir1 ;skip fill of ??? if not blank
		;set comfcb to all ??? for current disk
		mvi b,11 ;length of fill ????????.???
		dir0: mvi m,'?'! inx h! dcr b! jnz dir0
	;not a blank request, must be in comfcb
	dir1:	mvi e,0! push d ;E counts directory entries
		call searchcom ;first one has been found
		cz nofile ;not found message
	dir2:	jz endir
		;found, but may be system file
		lda dcnt ;get the location of the element
		rrc! rrc! rrc! ani 110$0000b! mov c,a
		;c contains base index into buff for dir entry
		mvi a,sysfile! call addhcf ;value to A
		ral! jc dir6 ;skip if system file
		;c holds index into buffer
		;another fcb found, new line?
		pop d! mov a,e! inr e! push d
		;e=0,1,2,3,...new line if mod 4 = 0
		ani 11b! push psw ;and save the test
			jnz dirhdr0 ;header on current line
			call crlf
			push b! call cselect! pop b
			;current disk in A
			adi 'A'! call printbc
			mvi a,':'! call printbc
			jmp dirhdr1 ;skip current line hdr
		dirhdr0:call blank ;after last one
			mvi a,':'! call printbc
		dirhdr1:
			call blank
		;compute position of name in buffer
		mvi b,1 ;start with first character of name
		dir3:	mov a,b! call addhcf ;buff+a+c fetched
			ani 7fh ;mask flags
			;may delete trailing blanks
			cpi ' '! jnz dir4 ;check for blank type
			pop psw! push psw ;may be 3rd item
			cpi 3! jnz dirb ;place blank at end if not
			mvi a,9! call addhcf ;first char of type
			ani 7fh! cpi ' '! jz dir5
			;not a blank in the file type field
		dirb:	mvi a,' ' ;restore trailing filename chr
		dir4:
			call printbc ;char printed
			inr b! mov a,b! cpi 12! jnc dir5
			;check for break between names
			cpi 9! jnz dir3 ;for another char
			;print a blank between names
			call blank! jmp dir3
		;
	dir5:	;end of current entry
		pop psw ;discard the directory counter (mod 4)
	dir6:	call break$key ;check for interrupt at keyboard
		jnz endir ;abort directory search
		call searchn! jmp dir2 ;for another entry
	endir:	;end of directory scan
		pop d ;discard directory counter
		jmp retcom
;
;
erase:
	call fillfcb0 ;cannot be all ???'s
	cpi 11
	jnz erasefile
		;erasing all of the disk
		lxi b,ermsg! call print!
		call readcom
		lxi h,comlen! dcr m! jnz ccp ;bad input
		inx h! mov a,m! cpi 'Y'! jnz ccp
		;ok, erase the entire diskette
		inx h! shld comaddr ;otherwise error at retcom
	erasefile:
		call setdisk
		lxi d,comfcb! call delete
		inr a ;255 returned if not found
		cz nofile ;no file message if so
		jmp retcom
;
	ermsg:	db	'ALL (Y/N)?',0
;
type:
	call fillfcb0! jnz comerr ;don't allow ?'s in file name
	call setdisk! call openc ;open the file
	jz typerr ;zero flag indicates not found
		;file opened, read 'til eof
		call crlf! lxi h,bptr! mvi m,255 ;read first buffer
		type0:	;loop on bptr
			lxi h,bptr! mov a,m! cpi 128 ;end buffer
			jc type1! push h ;carry if 0,1,...,127
			;read another buffer full
			call diskreadc! pop h ;recover address of bptr
			jnz typeof ;hard end of file
			xra a! mov m,a ;bptr = 0
		type1:	;read character at bptr and print
			inr m ;bptr = bptr + 1
			lxi h,buff! call addh ;h,l addresses char
			mov a,m! cpi eofile! jz retcom
			call printchar
			call break$key! jnz retcom ;abort if break
			jmp type0 ;for another character
		;
		typeof:	;end of file, check for errors
			dcr a! jz retcom
			call readerr
		typerr:	call resetdisk! jmp comerr
;
save:
		call getnumber; value to register a
		push psw ;save it for later
		;
		;should be followed by a file to save the memory image
		call fillfcb0
		jnz comerr ;cannot be ambiguous
		call setdisk ;may be a disk change
		lxi d,comfcb! push d! call delete ;existing file removed
		pop d! call make ;create a new file on disk
		jz saverr ;no directory space
		xra a! sta comrec; clear next record field
		pop psw ;#pages to write is in a, change to #sectors
		mov l,a! mvi h,0! dad h! 
		lxi d,tran ;h,l is sector count, d,e is load address
	save0:	;check for sector count zero
		mov a,h! ora l! jz save1 ;may be completed
		dcx h ;sector count = sector count - 1
		push h ;save it for next time around
		lxi h,128! dad d! push h ;next dma address saved
		call setdma ;current dma address set
		lxi d,comfcb! call diskwrite
		pop d! pop h ;dma address, sector count
		jnz saverr ;may be disk full case
		jmp save0 ;for another sector
		;
	save1:	;end of dump, close the file
		lxi d,comfcb! call close
		inr a; 255 becomes 00 if error
		jnz retsave ;for another command
	saverr:	;must be full or read only disk
		lxi b,fullmsg! call print
	retsave:
		;reset dma buffer
		call setdmabuff
		jmp retcom
		fullmsg: db 'NO SPACE',0
		;
;
rename:
	;rename a file on a specific disk
	call fillfcb0! jnz comerr ;must be unambiguous
	lda sdisk! push psw ;save for later compare
	call setdisk ;disk selected
	call searchcom ;is new name already there?
	jnz renerr3
		;file doesn't exist, move to second half of fcb
		lxi h,comfcb! lxi d,comfcb+16! mvi b,16! call move0
		;check for = or left arrow
		lhld comaddr! xchg! call deblank
		cpi '='! jz ren1 ;ok if =
		cpi la! jnz renerr2
	ren1:	xchg! inx h! shld comaddr ;past delimiter
		;proper delimiter found
		call fillfcb0! jnz renerr2
		;check for drive conflict
			pop psw! mov b,a ;previous drive number
			lxi h,sdisk! mov a,m! ora a! jz ren2
			;drive name was specified.  same one?
			cmp b! mov m,b! jnz renerr2
	ren2:	mov m,b ;store the name in case drives switched
		xra a! sta comfcb! call searchcom ;is old file there?
		jz renerr1
		;
		;everything is ok, rename the file
		lxi d,comfcb! call renam
		jmp retcom
		;
	renerr1:; no file on disk
		call nofile! jmp retcom
	renerr2:; ambigous reference/name conflict
		call resetdisk! jmp comerr
	renerr3:; file already exists
		lxi b,renmsg! call print! jmp retcom
		renmsg: db 'FILE EXISTS',0
;
user:
	;set user number
	call getnumber; leaves the value in the accumulator
	cpi 16! jnc comerr; must be between 0 and 15
	mov e,a ;save for setuser call
	lda comfcb+1! cpi ' '! jz comerr
	call setuser ;new user number set
	jmp endcom
;
userfunc:
	call serialize ;check serialization
	;load user function and set up for execution
	lda comfcb+1! cpi ' '! jnz user0
		;no file name, but may be disk switch
		lda sdisk! ora a! jz endcom ;no disk name if 0
		dcr a! sta cdisk! call setdiska ;set user/disk
		call select! jmp endcom
	user0:	;file name is present
		lxi d,comfcb+9! ldax d! cpi ' '! jnz comerr ;type ' '
		push d! call setdisk! pop d! lxi h,comtype ;.com
		call movename ;file type is set to .com
		call openc! jz userer
		;file opened properly, read it into memory
		lxi h,tran ;transient program base
		load0:	push h ;save dma address
			xchg! call setdma
			lxi d,comfcb! call diskread! jnz load1
			;sector loaded, set new dma address and compare
			pop h! lxi d,128! dad d
			lxi d,tranm ;has the load overflowed?
			mov a,l! sub e! mov a,h! sbb d! jnc loaderr
			jmp load0 ;for another sector
			;
		load1:	pop h! dcr a! jnz loaderr ;end file is 1
			call resetdisk ;back to original disk
			call fillfcb0! lxi h,sdisk! push h
			mov a,m! sta comfcb ;drive number set
			mvi a,16! call fillfcb ;move entire fcb to memory
			pop h! mov a,m! sta comfcb+16
			xra a! sta comrec ;record number set to zero
			lxi d,fcb! lxi h,comfcb! mvi b,33! call move0
			;move command line to buff
			lxi h,combuf
		bmove0:	mov a,m! ora a! jz bmove1! cpi ' '! jz bmove1
			inx h! jmp bmove0 ;for another scan
			;first blank position found
		bmove1:	mvi b,0! lxi d,buff+1! ;ready for the move
		bmove2:	mov a,m! stax d! ora a! jz bmove3
			;more to move
			inr b! inx h! inx d! jmp bmove2
		bmove3:	;b has character count
			mov a,b! sta buff
			call crlf
			;now go to the loaded program
			call setdmabuff ;default dma
			call saveuser ;user code saved
			;low memory diska contains user code
			call tran ;gone to the loaded program
			lxi sp,stack ;may come back here
			call setdiska! call select
			jmp ccp
		;
		userer:	;arrive here on command error
			call resetdisk! jmp comerr
			;
		loaderr:;cannot load the program
			lxi b,loadmsg! call print
			jmp retcom
			loadmsg: db 'BAD LOAD',0
		comtype:	db 'COM' ;for com files
;
;
retcom:	;reset disk before end of command check
	call resetdisk
;
endcom:	;end of intrinsic command
	call fillfcb0 ;to check for garbage at end of line
	lda comfcb+1! sui ' '! lxi h,sdisk! ora m
	;0 in accumulator if no disk selected, and blank fcb
	jnz comerr
	jmp ccp
;
;
;
;	data areas
	ds	16	;8 level stack
stack:
;
;	'submit' file control block
submit:	db	0	;00 if no submit file, ff if submitting
subfcb:	db	0,'$$$     '	;file name is $$$
	db	'SUB',0,0	;file type is sub
submod:	db	0	;module number
subrc:	ds	1	;record count filed
	ds	16	;disk map
subcr:	ds	1	;current record to read
;
;	command file control block
comfcb:	ds	32	;fields filled in later
comrec:	ds	1	;current record to read/write
dcnt:	ds	1	;disk directory count (used for error codes)
cdisk:	ds	1	;current disk
sdisk:	ds	1	;selected disk for current operation
			;none=0, a=1, b=2 ...
bptr:	ds	1	;buffer pointer
	end	ccploc