; --- NOTE ---
;   This was produced by scanning and OCRing the program listing
;   from Processor Technology Access newsletter, issue #2.  The
;   OCRing didn't go smoothly, and there was a LOT of hand editing.
;   After restoring the program listing, it was hand edited to be
;   compatible with the standard CP/M ASM assembler.  It assembles
;   and the hex output has been compared against the object listing
;   from the OCR'd listing and they match.  However, there is no
;  guarantee that this source is actually accurate.
;       Revision history:
;            99/11/06 -- jtb: first released version
;            99/11/13 -- jtb: fixed two source code OCR errors
; --- NOTE END ---
;
;
;
;
;        CUTER(TM)
;
;                 COPYRIGHT (C) 1977
;                 SOFTWARE TECHNOLOGY CORP.
;                 P.O. BOX 5260
;                 SAN MATEO, CA 94402
;                 (415) 349-8080
;
;    A L L    R I G H T S   R E S E R V E D ! ! !
;
;
;        VERSION  1.3
;                 77-03-27
;
;
;  THIS PROGRAM IS DESIGNED TO BE A STANDALONE CUTS
;  OPERATING SYSTEM. CUTER IS DESIGNED TO BE READ IN FROM
;  CASSETTE TAPE OR TO BE RESIDENT IN READ-ONLY-MEMORY.
;  CUTER SUPPORTS VARIOUS DEVICES INCLUDING SERIAL,
;  PARALLEL, THE PROCESSOR TECHNOLOGY VDM(TM) AND UP TO
;  TWO CUTS TAPE DRIVES.
;
;  CUTER(TM) HAS BEEN WRITTEN SO AS TO BE COMPATIBLE WITH
;  SOLOS(TM).  THE FOLLOWING KEYS ARE USED BY CUTER(TM)
;  IN PLACE OF THE SPECIAL KEYS ON THE SOL KEYBOARD:
;
;     CURSOR UP       CTL-W
;     CURSOR LEFT     CTL-A
;     CURSOR RIGHT    CTL-S
;     CURSOR DOWN     CTL-Z
;     CURSOR HOME     CTL-N
;     CLEAR SCREEN    CTL-K
;     MODE            CTL-@
;
;
;
	ORG	0C000H
;
;
;   AUTO-STARTUP CODE
;
START:	MOV	A,A	;SHOW THIS IS CUTER (SOLOS=00)
;      THIS BYTE ALLOWS AUTOMATIC POWER ON ENTRY
;      WHEN IN ROM SUPPORTING THIS HARDWARE FEATURE.
INIT:	JMP	STRTA	;SYSTEM RESTART ENTRY POINT
;
;   THESE JUMP POINTS ARE PROVIDED TO ALLOW COMMON ENTRY
; LOCATIONS FOR ALL VERSIONS OF CUTER.  THEY ARE USED
; EXTENSIVELY BY CUTS SYSTEM PROGRAMS AND IT IS RECOMMENDED
; THAT USER ROUTINES ACCESS CUTER ROUTINES THROUGH THESE
; POINTS ONLY!
;
RETRN:	JMP	COMND	;RETURN TO CUTER COMMAND PROCESSOR
FOPEN:	JMP	BOPEN	;CASSETTE OPEN FILE ENTRY
FCLOS:	JMP	PCLOS	;CASSETTE CLOSE FILE ENTRY
RDBYT:	JMP	RTBYT	;CASSETTE READ BYTE ENTRY
WRBYT:	JMP	WTBYT	;CASSETTE WRITE BYTE ENTRY
RDBLK:	JMP	RTAPE	;CASSETTE READ BLOCK ENTRY
WRBLK:	JMP	WTAPE	;CASSETTE WRITE BLOCK ENTRY
;
;     SYSTEM I/O ENTRY POINTS
;
;  THESE FOUR ENTRY POINTS ARE USED TO EITHER INPUT
;  OR OUTPUT TO CUTER PSUEDO PORTS.
;  THESE PSUEDO PORTS ARE AS FOLLOWS:
;
;  PORT   INPUT              OUTPUT
;  ----   -----------------  ---------------------
;   0     KEYBOARD INPUT     BUILT-IN VDM DRIVER
;         ACTUAL PORT 3      PORT C8, MEMORY FROM CC00
;   1     SERIAL PORT        SERIAL PORT
;         ACTUAL PORT 1      ACTUAL PORT 1
;   2     PARALLEL PORT      PARALLEL PORT
;         ACTUAL PORT 2      ACTUAL PORT 2
;   3     USER'S INPUT RTN   USER'S OUTPUT ROUTINE
;
;  STATUS FOR ACTUAL PORTS 1, 2 AND 3 IS VIA ACTUAL
;  PORT 0.  THE BITS OF PORT ZERO ARE DEFINED AS FOLLOWS:
;
;   :     :     :     :     :     :---- : --- : --- :
;   : TBE : RDA :     :     :     :PXDR : PDR : KDR :
; BIT  7     6     5     4     3     2     1     0
;
;  WHERE:
;    TBE    1=TRANSMITTER BUFFER EMPTY (SERIAL)
;    RDA    1=READER DATA AVAILABLE (SERIAL)
;    ----
;    PXDR   0=PARALLEL EXTERNAL DEVICE READY
;    ---
;    PDR    0=PARALLEL DATA READY
;    ---
;    KDR    0=KEYBOARD DATA READY
;
;
;
;
;  NOTE: SOUT AND SINP ARE "LDA" INSTRUCTIONS.
;        THIS FACT IS USED TO ALLOW ACCESS TO THE
;        BYTES "OPORT" AND "IPORT" DYNAMICALLY.
;        THESE MUST REMAIN "LDA" INSTRUCTIONS!!!!!
;
SOUT:	LDA	OPORT	;OUTPUT VIA STANDARD OUTPUT PSUEDO PORT
AOUT:	JMP	OUTPR	;OUTPUT VIA PSUEDO PORT SPECIFIED IN REG A
SINP:	LDA	IPORT	;INPUT VIA STANDARD INPUT PSUEDO PORT
AINP:	EQU	$	;INPUT VIA PSUEDO PORT SPECIFIED IN REG A
; -----------END OF SYSTEM ENTRY POINTS----------
;
;
; AINP CONTINUES HERE (IT COULD HAVE BEEN A "JMP" THOUGH)
	PUSH	H	;SAVE HL FM ENTRY
	LXI	H,ITAB
;
;    THIS ROUTINE PROCESSES THE I/O REQUESTS
;
IOPRC:	ANI	3	;KEEP REGISTER "A" TO FOUR VALUES
	RLC		;COMPUTE ENTRY ADDRESS
	ADD	L
	MOV	L,A	;WE HAVE ADDRESS
	JMP	DISPT	;DISPATCH TO IT
;
;
OUTPR:	EQU	$	;PROCESS OUTPUT REQUESTS
	PUSH	H	;SAVE REGS
	LXI	H,OTAB	;POINT TO OUTPUT DISPATCH TABLE
	JMP	IOPRC	;DISPATCH FOR PROPER PSUEDO PORT
;
;
;
; CUTER SYSTEM I/O ROUTINES
;
;
;    THIS ROUTINE IS A MODEL OF ALL INPUT ROUTINES WITHIN
;  CUTER.  THE FIRST ROUTINE "KREA1" PERFORMS THE INPUT
;  FROM THE STANDARD KEYBOARD ON PARALLEL PORT 3.
;  ALL STANDARD INPUT DRIVERS RETURN EITHER THE CHARACTER
;  WITH A NON-ZERO FLAG, OR JUST A ZERO FLAG INDICATING
;  THAT NO CHARACTER IS AVAILABLE YET.  IT WILL BE THE
;  RESPONSIBILITY OF THE USER TO LOOP WAITING FOR A
;  CHARACTER, OR TO USE THE INPUT AS A STATUS REQUEST.
;  WHEN A CHARACTER IS AVAILABLE, IT IS RETURNED IN REG A.
;
;  THE FOLLOWING KEYBOARD ROUTINE MAY BE USED AS A SAMPLE
;  OF HOW TO WRITE A USER INPUT ROUTINE.
;
;         KEYBOARD INPUT ROUTINE
;
KREA1:	EQU	$	;KEYBOARD READ ROUTINE
	IN	STAPT	;GET STATUS WORD
	CMA		;INVERT IT FOR PROPER RETURN
	ANI	KDR	;TEST NOT KEYBOARD DATA READY
	RZ		;ZERO IF NO CHARACTER RECEIVED
;
	IN	KDATA	;GET CHARACTER
	RET		;GO BACK WITH IT
;
;
;
;   SERIAL INPUT ROUTINE
;
SREA1:	EQU	$	;SERIAL INPUT ROUTINE
	IN	STAPT	;GET STATUS
	ANI	SDR	;TEST FOR SERIAL DATA READY
	RZ		;FLAGS ARE SET
;
	IN	SDATA	;GET DATA BYTE
;  IT IS UP TO THE CALLER TO STRIP PARITY IF DESIRED
	RET		;WE HAVE IT
;
;
;   SERIAL DATA OUTPUT
;
SEROT:	EQU	$	;SERIAL OUTPUT ROUTINE
	IN	STAPT	;GET STATUS
	RAL		;PUT HIGH BIT IN CARRY
	JNC	SEROT	;LOOP UNTIL TRANSMITTER BUFFER IS EMPTY
	MOV	A,B	;GET THE CHARACTER BACK
	OUT	SDATA	;SEND IT OUT
	RET		;AND WE'RE DONE
;
;
; PARALLEL DATA INPUT
PARIT:	EQU	$	;GET CHAR FM PARALLEL PORT
	IN	STAPT	;STATUS
	CMA		;INVERT FOR PROPER RETURN
	ANI	PDR	;IS DATA READY?
	RZ		;NO--JUST EXIT
	IN	PDATA	;YES--GET CHAR THEN
	RET		;THEN EXIT
;
;
;  PARALLEL DATA OUTPUT ROUTINE
PAROT:	EQU	$	;OUTPUT CHAR TO PARALLEL PORT
	IN	STAPT	;STATUS
	ANI	PXDR	;IS EXTERNAL DEVICE READY?
	JNZ	PAROT	;NO--WAIT TIL IT IS
	MOV	A,B	;GET CHAR
	OUT	PDATA	;SEND DATA NOW
	RET		;DONE
;
;
; USER DEFINED INPUT/OUTPUT ROUTINES
ERRIT:	EQU	$	;USER INPUT ROUTINE
	PUSH	H	;SAVE ORIG HL
	LHLD	UIPRT	;GET USER'S RTN ADDR
	JMP	ERRO1	;MERGE TO VERIFY THE ADDR
;
ERROT:	EQU	$	;USER OUTPUT ROUTINE
	PUSH	H	;SAVE ORIG HL
	LHLD	UOPRT	;GET USER'S RTN ADDR
ERRO1:	EQU	$	;WE MERGE HERE TO VFY ADDR
	MOV	A,L	;ZERO=UNDEFINED
	ORA	H	;IS IT?
	JNZ	DISP1	;NO--VALID--OFF TO IT
	JMP	STRTD	;RESET I/O PORTS AND BACK TO COMMAND MODE
;
;
;
;                  VIDEO DISPLAY ROUTINES
;
;
;  THESE ROUTINES ALLOW FOR STANDARD VIDEO TERMINAL
;  OPERATIONS.  ON ENTRY, THE CHARACTER FOR OUTPUT IS IN
;  REGISTER B AND ALL REGISTERS ARE UNALTERED ON RETURN.
;
;
;
VDM01:	EQU	$	;VDM OUTPUT DRIVER
	PUSH	H	;SAVE HL
	PUSH	D	;SAVE DE
	PUSH	B
;
;  PROCESS ESC SEQUENCE IF ANY
;
	LDA	ESCFL	;GET ESCAPE FLAG
	ORA	A
	JNZ	ESCS	;IF NON ZERO GO PROCESS THE REST OF THE SEQUENCE
;
	MOV	A,B	;GET CHAR
	ANI	7FH	;CLR HI BIT IN CASE
	MOV	B,A	;USE CHAR STRIPPED OF HI BIT FOR COMPATABILITY
	JZ	GOBK	;MAKE A QUICK EXIT FOR A NULL
;
	LXI	H,TBL
	CALL	TSRCH	;GO PROCESS
;
GOBACK:	EQU	$	;RESET CURSOR AND DELAY
	CALL	VDADD	;GET SCRN ADDR
	MOV	A,M	;GET CHAR
	ORI	80H	;INVERSE VIDEO
	MOV	M,A	;CURSOR IS NOW THERE
	LHLD	SPEED-1	;GET DELAY SPEED
	INR	L	;MAKE IT DEFINITELY NON-ZERO
	XRA	A	;DELAY ENDS WHEN H=ZERO
TIMER:	DCX	H	;LOOP FOR DELAY AMNT
	CMP	H	;IS IT DONE YET
	JNZ	TIMER	;NO--KEEP DELAYING
GOBK:	POP	B
	POP	D	;RESTORE ALL REGISTERS
	POP	H
	RET		;EXIT FROM VDMOT
;
;
NEXT:	EQU	$	;GO TO NEXT CHR
	INX	H
	INX	H
;
;  THIS ROUTINE SEARCHES FOR A MATCH OF THE CHAR IN "B"
;  TO THE CHAR IN THE TBL POINTED TO BY HL.
;
TSRCH:	MOV	A,M	;GET CHR FROM TABLE
	ORA	A	;SEE IF END OF TBL
	JZ	CHAR	;ZERO IS THE LAST
	CMP	B	;TEST THE CHR
	INX	H	;POINT FORWARD
	JNZ	NEXT
	PUSH	H	;FOUND ONE...SAVE ADDRESS
	CALL	CREM	;REMOVE CURSOR
	XTHL		;RESTORE ADDR OF CHAR ENTRY IN TBL
	JMP	DISPT	;DISPATCH FOR CURSOR CONTROL
;
;
CHAR:	EQU	$	;WE HAVE A CHAR
	MOV	A,B	;GET CHARACTER
	CPI	7FH	;IS IT A DEL?
	RZ		;GO BACK IF SO
;
;
;
OCHAR:	CALL	VDADD	;GET SCREEN ADDRESS
	MOV	M,B	;PUT CHR ON SCREEN
	LDA	NCHAR	;GET CHARACTER POSITION
	CPI	63	;END OF LINE?
	JC	OK
	LDA	LINE
	CPI	15	;END OF SCREEN?
	JNZ	OK
;
;   END OF SCREEN...ROLL UP ONE LINE
;
SCROLL:	XRA	A
	STA	NCHAR	;BACK TO FIRST CHAR POSITION
SROL:	MOV	C,A
	CALL	VDAD	;CALCULATE LINE TO BE BLANKED
	XRA	A
	CALL	CLIN1	;CLEAR IT
	LDA	BOT
	INR	A
	ANI	0FH
	JMP	ERAS3
;
;   INCREMENT LINE COUNTER IF NECESSARY
;
OK:	LDA	NCHAR	;GET CHR POSITION
	INR	A
	ANI	3FH	;MOD 64
	STA	NCHAR	;STORE THE NEW
	RNZ		;MORE CHARS THIS LINE
PDOWN:	EQU	$	;MOVE CURSOR DOWN ONE LINE
	LDA	LINE	;GET THE LINE COUNT
	INR	A
CURSC:	ANI	0FH	;MOD 15 INCREMENT
CUR:	STA	LINE	;STORE THE NEW
	RET
;
;    ERASE SCREEN
;
PERSE:	LXI	H,VDMEM	;POINT TO SCREEN
	MVI	M,80H+' '  ;THIS IS THE CURSOR
;
	INX	H	;NEXT CHAR
ERAS1:	EQU	$	;LOOP TO CLR SCRN
	MVI	M,' '	;BLANK IT OUT
	INX	H	;NEXT SCRN LOC
	MOV	A,H	;SEE IF DONE
	CPI	0D0H	;DID IT GO ABOVE VDM
	JC	ERAS1	;NO--MORE
	STC		;SAY WE WANT TO DROP THRU TO ERAS3
;
PHOME:	EQU	$	;RESET CURSOR TO HOME
	MVI	A,0	;CLEAR, LEAVE CARRY AS IS
	STA	LINE	;ZERO LINE
	STA	NCHAR	;LEFT SIDE OF SCREEN
	RNC		;THIS IS JUST A HOME OPERATION
;
ERAS3:	OUT	DSTAT	;RESET SCROLL PARAMETERS
	STA	BOT	;BEGINNING OF TEXT OFFSET
	RET
;
;
CLIN2:	EQU	$	;HERE TO SEE IF VDM OUTPUT
	LDA	OPORT	;GET CRNT OUTPUT PORT
	ORA	A
	RNZ		;NOT VDM--DONE THEN
CLINE:	CALL	VDADD	;GET CURRENT SCREEN ADDRESS
	LDA	NCHAR	;CURRENT CURSOR POSITION
CLIN1:	CPI	64	;NO MORE THAN 63
	RNC		;ALL DONE
	MVI	M,' '	;ALL SPACED OUT
	INX	H
	INR	A
	JMP	CLIN1	;LOOP TO END OF LINE
;
;
;  ROUTINE TO MOVE THE CURSOR UP ONE LINE
;
PUP:	LDA	LINE	;GET LINE COUNT
	DCR	A
	JMP	CURSC	;MERGE
;
;  MOVE CURSOR LEFT ONE POSITION
;
PLEFT:	LDA	NCHAR
	DCR	A
PCUR:	EQU	$	;TAKE CARE OF CURSOR SAME LINE
	ANI	03FH	;LET CURSOR WRAP AROUND
	STA	NCHAR	;UPDATED CURSOR
	RET
;
;     CURSOR RIGHT ONE POSITION
;
PRIT:	LDA	NCHAR
	INR	A
	JMP	PCUR
;
;   ROUTINE TO CALCULATE SCREEN ADDRESS
;
;   ENTRY AT:    RETURNS:
;
;         VDADD  CURRENT SCREEN ADDRESS
;         VDAD2  ADDRESS OF CURRENT LINE, CHAR 'C'
;         VDAD   LINE 'A', CHARACTER POSITION 'C'

VDADD:	LDA	NCHAR	;GET CHARACTER POSITION
	MOV	C,A	;'C' KEEPS IT
VDAD2:	LDA	LINE	;LINE POSITION
VDAD:	MOV	L,A	;INTO 'L'
	LDA	BOT	;GET TEXT OFFSET
	ADD	L	;ADD IT TO THE LINE POSITION
	RRC		;TIMES TWO
	RRC		;MAKES FOUR
	MOV	L,A	;L HAS IT
	ANI	3	;MOD THREE FOR LATER
	ADI	VDMEM SHR 8	;LOW SCREEN OFFSET
	MOV	H,A	;NOW H IS DONE
	MOV	A,L	;TWIST L'S ARM
	ANI	0C0H
	ADD	C
	MOV	L,A
	RET		;H & L ARE NOW PERVERTED
;
;    ROUTINE TO REMOVE CURSOR
;
CREM:	CALL	VDADD	;GET CURRENT SCREEN ADDRESS
	MOV	A,M
	ANI	7FH	;STRIP OFF THE CURSOR
	MOV	M,A
	RET
;
;     ROUTINE TO BACKSPACE
;
PBACK:	CALL	PLEFT
	CALL	VDADD	;GET SCREEN ADDRESS
	MVI	M,' '	;PUT A BLANK THERE
	RET
;
;     ROUTINE TO PROCESS A CARRIAGE RETURN
;
PCR:	CALL	CLINE	;CLEAR FROM CURRENT CURSOR TO END OF LINE
;  NOTE THAT A COMES BACK=64 WHICH WILL BE CLEARED AT PCUR
	JMP	PCUR	;AND STORE THE NEW VALUE
;
;   ROUTINE TO PROCESS LINEFEED
;
PLF:	LDA	LINE	;GET LINE COUNT
	INR	A	;NEXT LINE
	ANI	15	;SEE IF IT WRAPPED AROUND
	JNZ	CUR	;IT DID NOT--NO SCROLL
;
	JMP	SROL	;SCROLL ONE LINE--CURSOR SOME POSITION
;
;     SET ESCAPE PROCESS FLAG
;
PESC:	MVI	A,(-1) AND 0FFH
	STA	ESCFL	;SET FLAG
	RET
;
;       PROCESS ESCAPE SEQUENCE
;
ESCS:	CALL	CREM	;REMOVE CURSOR
	CALL	ESCSP	;PROCESS THE CHARACTER
	JMP	GOBACK
;
ESCSP:	LDA	ESCFL	;GET ESCAPE FLAG
	CPI	(-1) AND 0FFH	;TEST FLAG
	JZ	SECOND
;
;  PROCESS THIRD CHR OF ESC SEQUENCE
;
	LXI	H,ESCFL
	MVI	M,0
	CPI	2
	JC	SETX	;SET X
	JZ	SETY	;SET Y
	CPI	8	;SPECIAL SET SPEED
	JZ	STSPD	;YES--SET THE SPEED WITH IT THEN
	CPI	9
	JC	OCHAR	;PUT IT ON THE SCREEN
	RNZ
;
;  TAB ABSOLUTE TO VALUE IN REG B
;
SETX:	MOV	A,B
	JMP	PCUR
;
;  SET CURSOR TO LINE "B"
;
SETY:	MOV	A,B
	JMP	CURSC
;
;
;   PROCESS SECOND CHR OF ESC SEQUENCE
;
SECOND:	MOV	A,B
	CPI	3
	JZ	CURET
	CPI	4
	JNZ	ARET2
;
ARET:	MOV	B,H
	MOV	C,L	;PRESENT SCREEN ADDRESS TO BC FOR RETURN
ARET1:	POP	H	;RETURN ADDRESS
	POP	D	;OLD B
	PUSH	B
	PUSH	H
	XRA	A
ARET2:	STA	ESCFL
	RET
;
;
;     RETURN PRESENT SCREEN PARAMETERS IN BC
;
CURET:	LXI	H,NCHAR
	MOV	B,M	;CHARACTER POSITION
	INX	H
	MOV	C,M	;LINE POSITION
	JMP	ARET1
;
;
;
;                START UP SYSTEM
;
;   CLEAR SCREEN AND THE FIRST 256 BYTES OF GLOBAL RAM
;  THEN ENTER THE COMMAND MODE.
;
STRTA:	XRA	A
	MOV	C,A
	LXI	H,DFLTS	;CLEAR AFTER USER PORT ADDRESSES
;
CLERA:	MOV	M,A
	INX	H
	INR	C
	JNZ	CLERA
;
; DETERMINE THE DEFAULT PORTS
;     THIS COULD BECOME "MVI A,XX" FOR YOUR SPECIFIC PORTS
	IN	SENSE	;GET SWITCHES
;
	MOV	B,A	;SAVE IT
	ANI	3	;MAKE IT A VALID PORT
	STA	DFLTS+1	;SET DEFAULT OUTPUT PORT
	ORA	A	;SEE IF THIS THE VDM
	JNZ	STRTB	;NO--DO NOT RESET VDM
	LXI	SP,SYSTP	;SET UP THE STACK FOR CALL
	CALL	PERSE	;(REG A ASSUMED TO COME BACK ZERO)
STRTB:	EQU	$	;FINISH OFF THIS PORT THEN DO NEXT
	LXI	H,0	;USE FOR CLEARING USER ADDRESSES
	CPI	3	;IS IT A USER PORT
	JZ	STRTC	;YES-- DO NOT CLEAR IT
	SHLD	UOPRT	;NO--CLEAR ADDR
STRTC:	EQU	$	;OUTPUT PORT ALL SET
	MOV	A,B	;FM SENSE SWITCHES
	RAR
	RAR		;NEXT 2 BITS ARE INPUT PORT
	ANI	3	;VALID PORT
	STA	DFLTS	;THIS IS DEFAULT INPUT PORT
	CPI	3	;IS THIS ONE A USER PORT
	JZ	STRTD	;YES--DO NOT CLEAR IT THEN
	SHLD	UIPRT	;NO--FORCE USER ADDRESS ZERO
STRTD:	EQU	$	;1ST TIME INITIALIZATION ALL DONE NOW
	LHLD	DFLTS	;PICK UP DEFAULT PORTS
	SHLD	IPORT	;FORCE PORTS TO DEFAULT
COMN1:	EQU	$	;HERE TO TURN OFF TAPES, THEN COMMAND MODE
	XRA	A
	OUT	TAPPT	;BE SURE TAPES ARE OFF
;
;
;
;            =--  COMMAND MODE  --=
;
;
;   THIS ROUTINE GETS AND PROCESSES COMMANDS
;
COMND:	LXI	SP,SYSTP	;SET STACK POINTER
	CALL	PROMPT	;PUT PROMPT ON SCREEN
	CALL	GCLI0	;INIT TO GET COMMAND LINE
	CALL	COPRC	;PROCESS THE LINE
	JMP	COMND	;OVER AND OVER
;
;
;
;   THIS ROUTINE READS A COMMAND LINE FROM THE SYSTEM
;  KEYBOARD
;
;  C/R   TERMINATES THE SEQUENCE ERASING ALL CHARS TO THE
;        RIGHT OF THE CURSOR
;  L/F   TERMINATES THE SEQUENCE
;  ESC   RESETS TO COMMAND MODE.
;
GCLI0:	EQU	$	;HERE TO INIT FOR GCLIN
	LXI	H,INLIN-1	;PT TO CHAR IN FRONT OF INPUT BFR
	MVI	M,7	;MAKE SURE IT IS "BELL" TO KEEP FM DEL'ING TOO FAR
	INX	H	;NOW PT TO INPUT BFR
	SHLD	INPTR	;SAVE AS STARTING PTR
	MVI	A,80	;NUMBER OF CHARS IN LINE (MAX)
GCLI1:	EQU	$	;LOOP TO BLANK OUT LINE BFR
	MVI	M,' '	;BLANKS
	INX	H	;NEXT CHAR
	DCR	A	;FOR THIS COUNT
	JNZ	GCLI1	;ENTIRE LINE
GCLIN:	CALL	SINP	;READ INPUT DEVICE
	JZ	GCLIN
	ANI	7FH	;MAKE SURE NO X'80' BIT DURING CMND MODE
	JZ	STRTD	;IF EITHER MODE (OR CTL-@)
	MOV	B,A
	CPI	CR	;IS IT CR?
	JZ	CLIN2	;YES--TERMINATE LINE HERE (CLR IF VDM)
	CPI	LF	;IS IT A LINEFEED
	RZ		;YES--TERMINATE LINE AS IS
	LHLD	INPTR	;CRNT LINE PTR
	CPI	7FH	;DELETE CHR?
	JNZ	GCLI2	;NO--OK
	MVI	B,BACKS	;REPLACE IT
	DCX	H	;BACK LINE PTR UP TOO
	MVI	A,'G'-40H	;SEE IF A BELL
	CMP	M	;IS IT?
	JNZ	GCLI3	;NO--OK
	MOV	B,A	;YES--RING THE BELL THEN
GCLI2:	EQU	$	;STORE CHAR IN INPUT AREA
	MOV	M,B	;PLACE CHAR INTO LINE
	INX	H	;NEXT CHAR
GCLI3:	EQU	$	;SAVE NEW LINE PTR
	SHLD	INPTR	;SAVE PTR
;
CONT:	CALL	SOUT
	JMP	GCLIN
;
;
;
;
;      FIND AND PROCESS COMMAND
;
COPRC:	EQU	$	;PROCESS THIS COMMAND LINE
	CALL	STUP	;SETUP TO PROCESS INPUT LINE
	XCHG		;DE=ADDR
	LXI	H,START	;PREP SO THAT HL WILL PT TO CUTER LATER
	PUSH	H	;PLACE PTR TO CUTER ON STACK FOR LATER DISPT
	CALL	SCHR	;SCAN PAST BLANKS
	JZ	ERR1	;NO COMMAND?
	XCHG		;HL HAS FIRST CHR
	LXI	D,COMTAB	;POINT TO COMMAND TABLE
	CALL	FDCOM	;SEE IF IN PRIMARY TABLE
	CZ	FDCOU	;TRY CUSTOM ONLY IF NOT PRIMARY COMMAND
DISP0:	EQU	$	;HERE TO EITHER DISPATCH OR DO ERROR
	JZ	ERR2	;NOT IN EITHER TABLE
	INX	D	;PT DE TO ADDR OF RTN
	XCHG		;HL=ADDR OF ADDR OF RTN
; **** DROP THRU TO DISPT ***
;
; THIS ROUTINE DISPTACHES TO THE ADDR AT CONTENTS OF HL.
; HL ARE RESTORED PRIOR TO GOING TO ROUTINE.
;
DISPT:	EQU	$	;DISPATCH
	MOV	A,M	;LOW BYTE
	INX	H
	MOV	H,M	;HI BYTE
	MOV	L,A	;AND LO, HL NOW COMPLETE
DISP1:	EQU	$	;HERE TO GO OFF TO HL DIRECTLY
	XTHL		;HL RESTORED AND ADDR ON STACK
	MOV	A,L	;ALWAYS PASS L IN "A" (PRIMARILY FOR SET'S)
	RET		;OFF TO ROUTINE
;
;
;
;   THIS ROUTINE SEARCHES THROUGH A TABLE, POINTED TO
;  BY 'DE', FOR A DOUBLE CHARACTER MATCH OF THE 'HL'
;  MEMORY CONTENT.  IF NO MATCH IS FOUND THE SCAN ENDS
;  WITH THE ZERO FLAG SET, ELSE NON-ZERO SET.
;
FDCOU:	EQU	$	;HERE TO SCAN CUSTOM TABLE
	LXI	D,CUTAB	;PT TO CUSTOM RTN TBL
FDCOM:	LDAX	D
	ORA	A	;TEST FOR TABLE END
	RZ		;NOT FOUND POST THAT AND RETURN
	PUSH	H	;SAVE START OF SCAN ADDRESS
	CMP	M	;TEST FIRST CHR
	INX	D
	JNZ	NCOM
;
	INX	H
	LDAX	D
	CMP	M	;NOW SECOND CHARACTER
	JNZ	NCOM	;GOODNESS
;
	POP	H	;RETURN HL TO PT TO CHAR START
	ORA	A	;FORCE TO NON-ZERO FLAG
	RET		;LET CALLER KNOW
;
;
NCOM:	INX	D	;GO TO NEXT ENTRY
	INX	D
	INX	D
	POP	H	;GET BACK ORIGINAL ADDRESS
	JMP	FDCOM	;CONTINUE SEARCH
;
;
; SET UP TO PROCESS AN INPUT LINE
STUP:	EQU	$	;PREPARE WHETHER VDM OR NOT
	LXI	H,INLIN	;ASSUME NON-VDM INPUT
	SHLD	INPTR	;ALSO RESET PTR FOR NOW
	LDA	OPORT	;SEE IF IT IS VDM
	ORA	A	;IS IT THE VDM PORT
	RNZ		;NO--HL ARE SET PROPERLY
	CALL	CREM	;REMOVE CURSOR
	MVI	C,1	;GET VDM ADDR FM POSITION ONE
	JMP	VDAD2	;GET SCRN ADDR
;
;           COMMAND TABLE
;
;  THIS TABLE DESCRIBES THE VALID COMMANDS FOR CUTER
;
COMTAB:	EQU	$	;START OF KNOWN COMMANDS
	DW	'DU'	;DUMP
	DW	DUMP
	DW	'EN'	;ENTR
	DW	ENTER
	DW	'EX'	;EXEC
	DW	EXEC
	DW	'GE'	;GET
	DW	TLOAD
	DW	'SA'	;SAVE
	DW	TSAVE
	DW	'XE'	;XEQ
	DW	TXEQ
	DW	'CA'	;CAT
	DW	TLIST
	DW	'SE'	;SET COMMAND
	DW	CSET
	DW	'CU'	;CUSTOM COMMAND ENTER/CLEAR
	DW	CUSET
	DB	0	;END OF TABLE MARK
;
;
;               DISPLAY DRIVER COMMAND TABLE
;
;     THIS TABLE DEFINES THE CHARACTERS FOR SPECIAL
;  PROCESSING. IF THE CHARACTER IS NOT IN THE TABLE IT
;  GOES TO THE SCREEN.
;
TBL:	DB	CLEAR	;SCREEN
	DW	PERSE
	DB	UP	;CURSOR
	DW	PUP
	DB	DOWN	;"
	DW	PDOWN
	DB	LEFT	;"
	DW	PLEFT
	DB	RIGHT	;"
	DW	PRIT
	DB	HOME	;"
	DW	PHOME
	DB	CR	;CARRIAGE RETURN
	DW	PCR
	DB	LF	;LINE FEED
	DW	PLF
	DB	BACKS	;BACK SPACE
	DW	PBACK
	DB	ESC	;ESCAPE KEY
	DW	PESC
	DB	0	;END OF TABLE
;
;   OUTPUT DEVICE TABLE
;
OTAB:	DW	VDM01	;VDM DRIVER
	DW	SEROT	;SERIAL OUTPUT
	DW	PAROT	;PARALLEL OUTPUT
	DW	ERROT	;ERROR OR USER DRIVER HANDLER
;
;    INPUT DEVICE TABLE
;
ITAB:	DW	KREA1	;KEYBOARD INPUT
	DW	SREA1	;SERIAL INPUT
	DW	PARIT	;PARALLEL INPUT
	DW	ERRIT	;ERROR OR USER DRIVER HANDLER
;
;
;       SECONDARY COMMAND TABLE FOR SET COMMAND
;
SETAB:	DW	'TA'	;SET TAPE SPEED
	DW	TASPD
	DW	'S='	;SET DISPLAY SPEED
	DW	DISPD
	DW	'I='	;SET INPUT PORT
	DW	SETIN
	DW	'O='	;SET OUTPUT PORT
	DW	SETOT
	DW	'CI'	;SET CUSTOM DRIVER ADDRESS
	DW	SETCI
	DW	'CO'	;SET CUSTOM OUTPUT DRIVER ADDRESS
	DW	SETCO
	DW	'XE'	;SET HEADER XEQ ADDRESS
	DW	SETXQ
	DW	'TY'	;SET HEADER TYPE
	DW	SETTY
	DW	'N='	;SET NUMBER OF NULLS
	DW	SETNU
	DW	'CR'	;SET CRC (NORMAL OR IGNORE CRC ERRORS)
	DW	SETCR
	DB	0	;END OF TABLE MARK
; -*-
;
;
;      OUTPUT A CRLF FOLLOWED BY A PROMPT
;
PROMPT:	CALL	CRLF
	MVI	B,'>'	;THE PROMPT
	JMP	SOUT	;PUT IT ON THE SCREEN
;
CRLF:	MVI	B,LF	;LINE FEED
	CALL	SOUT
	MVI	B,CR	;CARRIAGE RETURN
	CALL	SOUT
	LDA	NUCNT	;GET COUNT OF NULLS TO OUTPUT
	MOV	C,A	;SAVE COUNT IN C
NULOT:	DCR	C
	RM		;COUNTED DOWN PAST ZERO (MAX COUNT IS X'7F')
	XRA	A	;HERE IS THE NULL
	CALL	OUTH	;OUTPUT IT
	JMP	NULOT	;LOOP FOR NUMBER OF NULLS
;
;
;  SCAN OVER UP TO 12 CHARACTERS LOOKING FOR A BLANK
;
SBLK:	MVI	C,12	;MAXIMUM COMMAND STRING
SBLK1:	LDAX	D
	CPI	BLANK
	JZ	SCHR	;GOT A BLANK NOW SCAN PAST IT
	INX	D
	CPI	'='	;A EQUAL WILL ALSO STOP US (AT NEXT CHAR)
	JZ	SCHR	;FOUND, DE PT TO NEXT CHAR
	DCR	C	;NO MORE THAN TWELVE
	JNZ	SBLK1
	RET		;GO BACK WITH ZERO FLAG SET
;
;
;  SCAN PAST UP TO 10 BLANK POSITIONS LOOKING FOR
; A NON BLANK CHARACTER.
;
SCHR:	MVI	C,10	;SCAN TO FIRST NON BLANK CHR WITHIN 10
SCHR1:	LDAX	D	;GET NEXT CHARACTER
	CPI	SPACE
	RNZ		;WE'RE PAST THEM
	INX	D	;NEXT SCAN ADDRESS
	DCR	C
	RZ		;COMMAND ERROR
	JMP	SCHR1	;KEEP LOOPING
;
;  THIS ROUTINE SCANS OVER CHARACTERS, PAST BLANKS AND
; CONVERTS THE FOLLOWING ADDRESS TO HEX.  ERRORS RETURN TO
; THE ERROR HANDLER.
;
SCONV:	CALL	SBLK
	JZ	ERR1
;
;  THIS ROUTINE CONVERTS ASCII DIGITS INTO BINARY FOLLOWING
; A STANDARD HEX CONVERSION.  THE SCAN STOPS WHEN AN ASCII
; SPACE IS ENCOUNTERED.  PARAMETER ERRORS REPLACE THE ERROR
; CHARACTER ON THE SCREEN WITH A QUESTION MARK.
;
SHEX:	LXI	H,0	;CLEAR H & L
SHE1:	LDAX	D	;GET CHARACTER
	CPI	20H	;IS IT A SPACE?
	RZ		;IF SO
	CPI	'/'
	RZ
	CPI	':'
	RZ
;
HCONV:	DAD	H	;MAKE ROOM FOR THE NEW ONE
	DAD	H
	DAD	H
	DAD	H
	CALL	HCOV1	;DO THE CONVERSION
	JNC	ERR1	;NOT VALID HEXIDECIMAL VALUE
	ADD	L
	MOV	L,A	;MOVE IT IN
	INX	D	;BUMP THE POINTER
	JMP	SHE1
;
HCOV1:	SUI	48	;REMOVE ASCII BIAS
	CPI	10
	RC		;IF LESS THAN 9
	SUI	7	;IT'S A LETTER??
	CPI	10H
	RET		;WITH TEST IN HAND
;
;
;  THIS ROUTINE WILL SEE IF A FIELD (OPERAND) IS PRESENT.
;  IF NOT, THEN HL WILL REMAIN AS THEY WERE ON ENTRY.
;  IF IT WAS PRESENT, THEN HL=THAT VALUE IN HEX.
;
PSCAN:	EQU	$	;OPTIONAL FIELD SCANNER
	CALL	SBLK	;SEE IF FIELD IS PRESENT
	RZ		;RETURN LEAVING HL AS THEY WERE ON ENTRY
	CALL	SHEX	;FIELD IS THERE, GO GET IT
	RET		;HL= EITHER OPTIONAL FIELD (HEX), OR AS IT WAS
;
;
;
;
;           DUMP COMMAND
;
;     THIS ROUTINE DUMPS CHARACTERS FROM MEMORY TO THE
;  CURRENT OUTPUT DEVICE.
;  ALL VALUES ARE DESPLAYED AS ASCII HEX.
;
;  THE COMMAND FORM IS AS FOLLOWS:
;
;        DUMP  ADDR1  ADDR2
;
;    THE VALUES FROM ADDR1 TO ADDR2 ARE THEN OUTPUT TO THE
;  OUTPUT DEVICE.  IF ONLY ADDR1 IS SPECIFIED THEN THE
;  VALUE AT THAT ADDRESS IS OUTPUT.
;
;  IF WHILE DUMPING, THE MODE KEY IS PRESSED, THE DUMP WILL
;  BE TERMINATED.  IF THE SPACE BAR IS PRESSED, THE DUMP
;  WILL BE TEMPORARILY SUSPENDED UNTIL ANY KEY IS PRESSED.
;
DUMP:	EQU	$	;SET UP REGS TO DUMP SPECIFIED AREA
	CALL	SCONV	;GET START ADDR (REQUIRED)
	PUSH	H	;SAVE THE START ADDR
	CALL	PSCAN	;GET OPTIONAL END ADDR, HL=THIS OR START ADDR
	POP	D	;DE=START ADDR
	XCHG		;DE=END ADDR, HL=START ADDR NOW
;
DLOOP:	CALL	CRLF
	CALL	ADOUT	;OUTPUT ADDRESS
	CALL	BOUT	;ANOTHER SPACE TO KEEP IT PRETTY
	MVI	C,16	;VALUES PER LINE
;
DLP1:	MOV	A,M	;GET THE CHR
	PUSH	B	;SAVE VALUE COUNT
	CALL	HBOUT	;SEND IT OUT WITH A BLANK
	MOV	A,H	;CRNT ADDR
	CMP	D	;VERSUS ENDING ADDR
	JC	DLP1A	;NOT DONE YET
	MOV	A,L	;TRY LOW ORDER BYTE
	CMP	E
	JNC	COMND	;ALL DONE WHEN CRNT REACHES ENDING
DLP1A:	EQU	$	;HERE TO KEEP DUMPING
	POP	B	;VALUES PER LINE
	INX	H
	DCR	C	;BUMP THE LINE COUNT
	JNZ	DLP1	;NOT ZERO IF MORE FOR THIS LINE
	JMP	DLOOP	;DO A LFCR BEFORE THE NEXT
;
;    OUTPUT HL AS HEX 16 BIT VALUE
;
ADOUT:	MOV	A,H	;H FIRST
	CALL	HEOUT
	MOV	A,L	;THEN L FOLLOWED BY A SPACE
;
HBOUT:	CALL	HEOUT
	CALL	SINP	;SEE IF WE SHD ESCAPE FM DUMP
	JZ	BOUT	;NO--ADD THE SPACE THEN
	ANI	7FH	;MAKE SURE ITS CLEAR OF PARITY
	JZ	COMND	;EITHER MODE (OR CTL-@)
	CPI	' '	;IS IT SPACE
	JNZ	BOUT	;NO--IGNORE THE CHAR
WTLP1:	CALL	SINP	;ON SPACE, WAIT FOR ANY OTHER CHAR
	JZ	WTLP1	;JUST LOOP AFTER A SPACE UNTIL ANY KEY PRESSED
BOUT:	MVI	B,' '
	JMP	SOUT	;PUT IT OUT
;
HEOUT:	MOV	C,A	;GET THE CHARACTER
	RRC
	RRC		;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	CALL	HEOU1	;PUT THEM OUT
	MOV	A,C	;THIS TIME THE LOW FOUR
;
HEOU1:	ANI	0FH	;FOUR ON THE FLOOR
	ADI	48	;WE WORK WITH ASCII HERE
	CPI	58	;0-9?
	JC	OUTH	;YUP!
	ADI	7	;MAKE IT A LETTER
OUTH:	MOV	B,A	;OUTPUT IT FROM REGISTER 'B'
	JMP	SOUT
;
;
;           ENTR COMMAND
;
;   THIS ROUTINE GETS VALUES FROM THE KEYBOARD AND ENTERS
; THEM INTO MEMORY.  THE INPUT VALUES ARE SCANNED FOLLOWING
; A STANDARD 'GCLIN' INPUT SO ON-SCREEN EDITING MAY TAKE
; PLACE PRIOR TO THE LINE TERMINATOR.  A SLASH '/'
; ENDS THE ROUTINE AND RETURNS CONTROL TO THE COMMAND MODE.
;
ENTER:	CALL	SCONV	;SCAN OVER CHARS AND GET ADDRESS
	PUSH	H	;SAVE ADDRESS
;
ENLOP:	CALL	CRLF
	MVI	B,':'
	CALL	SOUT	;DSPLY THE COLON
	CALL	GCLI0	;INIT AND PROCESS A LINE
	CALL	STUP	;SET UP TO PROCESS INPUT LINE
	XCHG		;....TO DE
;
;
ENLO1:	MVI	C,3	;NO MORE THAN THREE SPACES BETWEEN VALUES
	CALL	SCHR1	;SCAN TO NEXT VALUE
	JZ	ENLOP	;LAST ENTRY FOUND START NEW LINE
;
	CPI	'/'	;COMMAND TERMINATOR?
	JZ	COMND	;IF SO...
	CALL	SHEX	;CONVERT VALUE
	CPI	':'	;ADDRESS TERMINATOR?
	JZ	ENLO3	;GO PROCESS IF SO
	MOV	A,L	;GET LOW PART AS CONVERTED
	POP	H	;GET MEMORY ADDRESS
	MOV	M,A	;PUT IN THE VALUE
	INX	H
	PUSH	H	;BACK GOES THE ADDRESS
	JMP	ENLO1	;CONTINUE THE SCAN
;
ENLO3:	XTHL		;PUT NEW ADDRESS ON STACK
	INX	D	;MOVE SCAN PAST TERMINATOR
	JMP	ENLO1
;
;
;              EXECUTE COMMAND
;
;   THIS ROUTINE GETS THE FOLLOWING PARAMETER AND DOES A
; PROGRAM JUMP TO THE LOCATION GIVEN BY IT.  IF PROPER
; STACK OPERATIONS ARE USED WITHIN THE EXTERNAL PROGRAM
; IT CAN DO A STANDARD 'RET'URN TO THE CUTER COMMAND MODE.
;
;
EXEC:	CALL	SCONV	;SCAN PAST BLANKS AND GET PARAMETER
EXEC1:	EQU	$	;HERE TO GO TO HL
	PUSH	H	;SAVE ON STACK
	LXI	H,START	;LET USER KNOW WHERE WE ARE
	RET		;AND OFF TO USER
;
;
;
;
;   THIS ROUTINE GETS A NAME OF UP TO 5 CHARACTERS
;  FROM THE INPUT STRING.  IF THE TERMINATOR IS A
;  SLASH (/) THEN THE CHARACTER FOLLOWING IS TAKEN
;  AS THE CASSETTE UNIT SPECIFICATION.
;
;
NAME0:	EQU	$	;ENTER HERE TO SET HL TO THEAD
	LXI	H,THEAD	;PT WHERE TO PUT NAME
NAME:	CALL	SBLK	;SCAN OVER TO FIRST CHRS
	MVI	B,6
;
NAME1:	LDAX	D	;GET CHARACTER
	CPI	' '	;NO UNIT DELIMITER
	JZ	NFIL
	CPI	'/'	;UNIT DELIMITER
	JZ	NFIL
	MOV	M,A
	INX	D	;BUMP THE SCAN POINTER
	INX	H
	DCR	B
	JNZ	NAME1	;NAME IS OK, FALL THRU TO 'ERR1' IF NOT
;
;     CUTER ERROR HANDLER
;
ERR1:	XCHG		;GET SCAN ADDRESS
ERR2:	MVI	M,'?'	;FLAG THE ERROR
	LDA	OPORT	;SEE IF VIA VDM DRIVER
	ORA	A
	JZ	COMND	;YES--VDM SCREEN NOW HAS THE ?
	CALL	CRLF
	MVI	B,'?'	;SET UP THE ????
	CALL	SOUT	;INDICATE INPUT NOT VALID
	JMP	COMND	;NOW READY FOR NEXT INPUT
;
;
;
;  HERE WE HAVE SCANNED OFF THE NAME. ZERO FILL IN FOR
;  NAMES LESS THAN FIVE CHARACTERS.
;
NFIL:	MVI	M,0	;PUT IN AT LEAST ONE ZERO
	INX	H
	DCR	B
	JNZ	NFIL	;LOOP UNTIL B IS ZERO
;
	CPI	'/'	;IS THERE A UNIT SPECIFICATION?
	MVI	A,1	;PRETEND NOT
	JNZ	DEFLT
	INX	D	;MOVE PAST THE TERMINATOR
	CALL	SCHR	;GO GET IT
	SUI	'0'	;REMOVE ASCII BIAS
;
DEFLT:	EQU	$	;CNVRT TO INTERNAL BIT FOR TAPE CONTROL
	ANI	1	;JUST BIT ZERO
	MVI	A,TAPE1	;ASSUME TAPE ONE
	JNZ	STUNT	;IF NON ZERO, IT IS ONE
	RAR		;ELSE MAKE IT TAPE TWO
STUNT:	STA	FNUMF	;SET IT IN
	RET
;
;
;
;   THIS ROUTINE PROCESSES THE XEQ AND GET COMMANDS
;
;
TXEQ:	DB	3EH	;THIS BEGINS "MVI" OF THE "XRA" FOLLOWING
TLOAD:	XRA	A	;A=0 TLOAD, A=AF (#0) THEN XEQ
	PUSH	PSW	;SAVE FLAG TO SAY WHETHER LOAD OR XEQ
	LXI	H,DHEAD	;PLACE DUMMY HDR HERE FOR COMPARES
	CALL	NAME	;SET IN NAME AND UNIT
	LXI	H,0	;ASSUME LOAD ADDR NOT GIVEN
	CALL	PSCAN	;HL EITHER =0, OR OVERRIDE LOAD ADDR
;
TLOA2:	XCHG		;PUT ADDRESS IN DE
	LXI	H,DHEAD	;PT TO NORMAL HDR
	MOV	A,M	;GET 1ST CHAR OF NAME
	ORA	A	;IS THERE A NAME?
	JNZ	TLOA3	;YES--LOOK FOR IT
	LXI	H,THEAD	;PT TO SAME HDR TO LOAD NEXT FILE
TLOA3:	PUSH	H	;SAVE PTR TO WHICH HDR TO USE
	CALL	ALOAD	;GET UNIT AND SPEED
	POP	H	;RESTORE PTR TO PROPER HDR TO USE
	CALL	RTAPE	;READ IN THE TAPE
	JC	TAERR	;TAPE ERROR?
;
	CALL	NAOUT	;PUT OUT THE HEADER PARAMETERS
	POP	PSW	;RESTORE FLAG SAYING WHETHER IT WAS LOAD OR XEQ
	ORA	A
	RZ		;AUTO XEQ NOT WANTED
	LDA	HTYPE	;CHECK TYPE
	ORA	A	;SET FLAGS
	JM	TAERR	;TYPE IS NON XEQ
	LDA	THEAD+5
	ORA	A
	JNZ	TAERR	;THE BYTE MUST BE ZERO FOR AUTO XEQ
	LHLD	XEQAD	;GET THE TAPE ADDRESS
	JMP	EXEC1	;AND GO OFF TO IT
;
;
;
;   THIS ROUTINE IS USED TO SAVE PROGRAMS AND DATA ON
;   THE CASSETTE UNIT.
;
;
TSAVE:	EQU	$	;SAVE MEMORY IMAGE TO TAPE
	CALL	NAME0	;GET NAME AND UNIT
	CALL	SCONV	;GET START ADDRESS
	PUSH	H	;SAVE START ADDR FOR SIZE COMPUTATION LATER
	CALL	SCONV	;GET END ADDR (REQUIRED)
	XTHL		;HL=START ADDR NOW, STACK=END ADDR
	PUSH	H	;STACK =START FOLLOWED BY END
	CALL	PSCAN	;SEE IF RETRIEVE FROM ADDR
	SHLD	LOADR	;EITHER ACTUAL START, OR OVERRIDE INTO HDR
	POP	H	;;HL=START ADDR
	POP	D	;DE=END ADDR
	PUSH	H	;PUT START BACK ONTO STACK
	MOV	A,E	;SIZE=END-START+1
	SUB	L
	MOV	L,A
	MOV	A,D
	SBI	0	;THIS EQUALS A "SBB H"
	SUB	H	;THIS IS NEEDED
	MOV	H,A
	INX	H
	SHLD	BLOCK	;STORE THE SIZE
	PUSH	H	;SAVE AS THE BLOCK SIZE
;
	CALL	ALOAD	;GET UNIT AND SPEED
	LXI	H,THEAD	;PT TO HEADER TO WRITE
	CALL	WHEAD	;TURN TAPE ON, THEN WRITE HEADER
	POP	D	;GET BACK THE SIZE
	POP	H	;AND GET BACK THE ACTUAL START ADDR
	JMP	WTAP1	;WRITE THE BLK (W/EXTRA PUSH)
;
;   OUTPUT ERROR AND HEADER
;
TAERR:	CALL	CRLF
	MVI	D,6
	LXI	H,ERRM
	CALL	NLOOP	;OUTPUT ERROR
	CALL	NAOUT	;THEN THE HEADER
	JMP	COMN1
;
ERRM:	DB	'ERROR '

;
;
;              CAT COMMAND
;
;   THIS ROUTINE READS HEADERS FROM THE TAPE AND OUTPUTS
;   THEM TO THE OUTPUT DEVICE.  IT CONTINUES UNTIL THE
;   MODE KEY IS DEPRESSED.
;
TLIST:	EQU	$	;PRODUCE A LIST OF FILES ON A TAPE
	CALL	NAME0	;GET UNIT IF ANY (NAME IS IGNORED)
	CALL	CRLF	;START ON A FRESH LINE
;
;
LLIST:	CALL	ALOAD
	MVI	B,1
	CALL	TON	;TURN ON THE TAPE
LIST1:	CALL	RHEAD
	JC	COMN1	;TRUN OFF THE TAPE UNIT
	JNZ	LIST1
	CALL	NAOUT	;OUTPUT THE HEADER
	JMP	LLIST
;
;
;   THIS ROUTINE GETS THE CASSETTE UNIT NUMBER AND
;   SPEED TO REGISTER "A" FOR THE TAPE CALLS
;
ALOAD:	LXI	H,FNUMF	;POINT TO THE UNIT SPECIFICATION
	LDA	TSPD	;GET THE TAPE SPEED
	ORA	M	;PUT THEM TOGETHER
	RET		;AND GO BACK
;
;   THIS ROUTINE OUTPUTS THE NAME AND PARAMETERS OF
;   THEAD TO THE OUTPUT DEVICE.
;
;
NAOUT:	MVI	D,8
	LXI	H,THEAD-1  ;POINT TO THE HEADER
	CALL	NLOOP	;OUTPUT THE HEADER
	CALL	BOUT	;ANOTHER BLANK
	LHLD	LOADR	;NOW THE LOAD ADDRESS
	CALL	ADOUT	;PUT IT OUT
	LHLD	BLOCK	;AND THE BLOCK SIZE
	CALL	ADOUT
	JMP	CRLF	;DO THE CRLF AND RETURN
;
;
NLOOP:	MOV	A,M	;GET CHARACTER
	ORA	A
	JNZ	CHRLI	;IF IT ISN'T A ZERO
	MVI	A,' '	;SPACE OTHERWISE
CHRLI:	EQU	$	;CHAR IS OK TO SEND
	CALL	OUTH	;OUTPUT IT FROM A REG
	INX	H
	DCR	D
	JNZ	NLOOP
	RET
;
;
;
;
;      "SET" COMMAND
;
;   THIS ROUTINE GETS THE ASSOCIATED PARAMETER AND
;   DISPATCHES TO THE PROPER ROUTINE FOR SETTING
;   MEMORY VALUES.
;
CSET:	CALL	SBLK	;SCAN TO SECONDARY COMMAND
	JZ	ERR1	;MUST HAVE AT LEAST SOMETHING!!
	PUSH	D	;SAVE SCAN ADDRESS
	CALL	SCONV	;CONVERT FOLLOWING VALUE
	XTHL		;HL=SAVED SCAN ADDR AND STACK=VALUE
	LXI	D,SETAB	;SECONDARY COMMAND TABLE
	CALL	FDCOM	;TRY TO LOCATE IT
	JMP	DISP0	;OFF TO IT OR ERROR IF NOT IN TBL
;
;
;  THIS ROUTINE SETS THE TAPE SPEED
;
TASPD:	EQU	$	;GET CONVERTED VALUE
	ORA	A	;IS IT ZERO?
	JZ	SETSP	;YES--THAT IS A PROPER SPEED
	MVI	A,32	;NO--SET SPEED PROPERLY THEN
SETSP:	STA	TSPD
	RET
;
;
STSPD:	EQU	$	;VDM ESCAPE SEQUENCE COMES HERE
	MOV	A,B	;GET CHAR FOR FOLLOWING DISPD
DISPD:	EQU	$	;SET DISPLAY SPEED
	STA	SPEED
	RET
;
;
SETIN:	EQU	$	;SET AN INPUT PSUEDO PORT
	STA	IPORT
	RET
;
;
SETOT:	EQU	$	;SET AN OUTPUT PSUEDO PORT
	STA	OPORT
	RET
;
;
SETCI:	EQU	$	;DEFINE USER INPUT RTN ADDR
	SHLD	UIPRT
	RET
;
;
SETCO:	EQU	$	;DEFINE USER OUTPUT RTN ADDR
	SHLD	UOPRT
	RET
;
;
SETTY:	EQU	$	;SET TAPE HDR TYPE
	STA	HTYPE
	RET
;
;
SETXQ:	EQU	$	;SET TAPE-EXECUTE ADDDR FOR HDR
	SHLD	XEQAD
	RET
;
;
SETNU:	EQU	$	;HERE TO SET NUMBER OF NULLS
	STA	NUCNT	;THIS IS IT
	RET
;
;
SETCR:	EQU	$	;SET CRC TO BE NORMAL, OR IGNORE CRC ERRORS
	STA	IGNCR	;FF=IGNORE CRC ERRORS, ELSE=NORMAL
	RET
;
;
CUSET:	EQU	$	;TRY TO SET/CLEAR CUSTOM ROUTINE ADDR
	CALL	NAME0	;GET A NAME (S/B 2 CHARS OR MORE)
	LXI	H,COMND	;PT HERE IN CASE ADDR NOT GIVEN
	CALL	PSCAN	;GET OPTIONAL OPERAND IF ANY
	PUSH	H	;SAVE THAT VALUE (IF ANY)
	LXI	H,THEAD	;PT TO NAME
	CALL	FDCOU	;SEE IF NAME IS KNOWN IN CUST TABLE
	JZ	CUSE2	;NO--PROCEED TO KNOW IT
	DCX	D	;DE PT TO 1ST CHAR OF NAME IN TBL
	MVI	M,0	;(HL CAME BACK PT'ING TO THEAD)  CLR THIS NAME
CUSE2:	EQU	$	;ENTER NEW ONE IN TBL
	MOV	A,M	;GET 1ST CHAR OF NAME
	STAX	D	;PUT NAME INTO TABLE
	INX	D
	INX	H
	MOV	A,M	;GET 2ND CHAR OF NAME
	STAX	D	;NAME IS NOW POSTED
	INX	D	;PT TO 1ST BYTE OF ADDR
	POP	H	;RESTORE SAVED RTN ADDR
	XCHG		;DE=RTN ADDR, HL=THIS CU ENTRY
	MOV	M,E	;LO BYTE
	INX	H
	MOV	M,D	;AND HI BYTE
	RET		;ALL DONE
;
;
; -*-
;
;
;
;
;   THE FOLLOWING ROUTINES PROVIDE "BYTE BY BYTE" ACCESS
;  TO THE CASSETTE TAPES ON EITHER A READ OR WRITE BASIS.
;
;  THE TAPE IS READ ONE BLOCK AT A TIME AND INDIVIDUAL
;  TRANSFERS OF DATA HANDLED BY MANAGING A BUFFER AREA.
;
;  THE BUFFER AREA IS CONTROLLED BY A FILE CONTROL BLOCK
;  (FCB) WHOSE STRUCTURE IS:
;
;
;     7 BYTES FOR EACH OF THE TWO FILES STRUCTURED AS
;   FOLLOWS:
;
;         1 BYTE -  ACCESS CONTROL   00 IF CLOSED
;                                    FF IF READING
;                                    FE IF WRITING
;         1 BYTE -  READ COUNTER
;         1 BYTE -  BUFFER POSITION POINTER
;         2 BYTE -  CONTROL HEADER ADDRESS
;         2 BYTE -  BUFFER LOCATION ADDRESS
;
;
;
;        THIS ROUTINE "OPENS" THE CASSETTE UNIT FOR ACCESS
;
;   ON ENTRY:  A - HAS THE TAPE UNIT NUMBER (1 OR 2)
;             HL - HAS USER SUPPLIED HEADER FOR TAPE FILE
;
;
;   NORMAL RETURN:   ALL REGISTERS ARE ALTERED
;                    BLOCK IS READY FOR ACCESS
;
;   ERROR RETURN:    CARRY BIT IS SET
;
;   ERRORS:  BLOCK ALREADY OPEN
;
;
BOPEN:	PUSH	H	;SAVE HEADER ADDRESS
	CALL	LFCB	;GET ADDRESS OF FILE CONTROL
	JNZ	TERE2	;FILE WAS ALREADY OPEN
	MVI	M,1	;NOW IT IS
	INX	H	;POINT TO READ COUNT
	MOV	M,A	;ZERO
	INX	H	;POINT TO BUFFER CURSOR
	MOV	M,A	;PUT IN THE ZERO COUNT
;
;  ALLOCATE THE BUFFER
;
	LXI	D,FBUF1	;POINT TO BUFFER AREA
	LDA	FNUMF	;GET WHICH ONE WE ARE GOING TO USE
	ADD	D
	MOV	D,A	;256 BIT ADD
;
UBUF:	POP	B	;HEADER ADDRESS
	ORA	A	;CLEAR CARRY AND RETURN AFTER STORING PARAMS
	JMP	PSTOR	;STORE THE VALUES
;
;    GENERAL ERROR RETURN POINTS FOR STACK CONTROL
;
TERE2:	POP	H
TERE1:	POP	D
TERE0:	XRA	A	;CLEAR ALL FLAGS
	STC		;SET ERROR
	RET
;
;
EOFER:	DCR	A	;SET MINUS FLAGS
	STC		;AND CARRY
	POP	D	;CLEAR THE STACK
	RET		;THE FLAGS TELL ALL
;
;
;
;
;   THIS ROUTINE CLOSES THE FILE BUFFER TO ALLOW ACCESS
;   FOR A DIFFERENT CASSETTE OR PROGRAM.  IF THE FILE
;   OPERATIONS WERE "WRITE" THEN THE LAST BLOCK IS WRITTED
;   OUT AND AN "END OF FILE" WRITTEN TO THE TAPE.  IF
;   THE OPERATIONS WERE "READS" THEN THE FILE IS JUST
;   MADE READY FOR NEW USE.
;
;   ON ENTRY:  A - HAS WHICH UNIT (1 OR 2)
;
;   ERROR RETURNS:  FILE WASN'T OPEN
;
;
PCLOS:	CALL	LFCB	;GET CONTROL BLOCK ADDRESS
	RZ		;WASN'T OPEN, CARRY IS SET FROM LFCB
	ORA	A	;CLEAR CARRY
	INR	A	;SET CONDITION FLAGS
	MVI	M,0	;CLOSE THE CONTROL BYTE
	RZ		;WE WERE READING...NOTHING MORE TO DO
;
;    THE FILE OPERATIONS WERE "WRITES"
;
;  PUT THE CURRENT BLOCK ON THE TAPE
;  (EVEN IF ONLY ONE BYTE)
;  THEN WRITE AN END OF FILE TO THE TAPE
;
;
	INX	H
	INX	H
	MOV	A,M	;GET CURSOR POSITION
	MOV	A,M	;GET CURSOR POSITION
	CALL	PLOAD	;BC GET HEADER ADDRESS, DE BUFFER ADDRESS
	PUSH	B	;HEADER TO STACK
	LXI	H,BLKOF	;OFFSET TO BLOCK SIZE
	DAD	B
	ORA	A	;TEST COUNT
	JZ	EOFW	;NO BYTES...JUST WRITE EOF
;
;    WRITE LAST BLOCK
;
	PUSH	H	;SAVE BLOCK SIZE POINTER FOR EOF
	MOV	M,A	;PUT IN COUNT
	INX	H
	MVI	M,0	;ZERO THE HIGHER BYTE
	INX	H
	MOV	M,E	;BUFFER ADDRESS
	INX	H
	MOV	M,D
	MOV	H,B
	MOV	L,C	;PUT HEADER ADDRESS IN HL
	CALL	WFBLK	;GO WRITE IT OUT
	POP	H	;BLOCK SIZE POINTER
;
;   NOW WRITE END OF FILE TO CASSETTE
;
EOFW:	XRA	A	;PUT IN ZEROS FOR SIZE:  EOF MARK IS ZERO BYTES!
	MOV	M,A
	INX	H
	MOV	M,A
	POP	H	;HEADER ADDRESS
	JMP	WFBLK	;WRITE IT OUT AND RETURN
;
;
;
;
;   THIS ROUTINE LOCATES THE FILE CONTROL BLOCK POINTED TO
;   BY REGISTER "A".  ON RETURN HL POINT TO THE CONTROL BYT
;   AND REGISTER "A" HAS THE CONTROL WORD WITH THE FLAGS
;   SET FOR IMMEDIATE CONDITION DECISIONS.
;
;
LFCB:	LXI	H,FCBAS	;POINT TO THE BASE OF IT
	RAR		;MOVE THE 1 & 2 TO 0 & 1 LIKE COMPUTERS LIKE
	ANI	1	;SMALL NUMBERS ARE THE RULE
	STA	FNUMF	;CURRENT ACCESS FILE NUMBER
	JZ	LFCB1	;UNIT ONE (VALUE OF ZERO)
	LXI	H,FCBA2	;UNIT TWO--PT TO ITS FCB
LFCB1:	EQU	$	;HL PT TO PROPER FCB
	MOV	A,M	;PICK UP FLAGS FM FCB
	ORA	A	;SET FLAGS BASED ON CONTROL WORD
	STC		;SET CARRY IN CASE OF IMMEDIATE ERROR RETURN
	RET
;
;
;
;
;    READ TAPE BYTE ROUTINE
;
;    ENTRY:       -  A -  HAS FILE NUMBER
;    EXIT: NORMAL -  A -  HAS BYTE
;          ERROR
;            CARRY SET     - IF FILE NOT OPEN OR
;                            PREVIOUS OPERATIONS WERE WRITE
;            CARRY & MINUS - END OF FILE ENCOUNTERED
;
;
;
;
RTBYT:	CALL	LFCB	;LOCATE THE FILE CONTROL BLOCK
	RZ		;FILE NOT OPEN
	INR	A	;TEST IF FF
	JM	TERE0	;ERROR WAS WRITING
	MVI	M,(-1) AND 0FFH	;SET IT AS READ  (IN CASE IT WAS JUST OPENED)
	INX	H
	MOV	A,M	;GET READ COUNT
	PUSH	H	;SAVE COUNT ADDRESS
	INX	H
	CALL	PLOAD	;GET THE OTHER PARAMETERS
	POP	H
	ORA	A
	JNZ	GTBYT	;IF NOT EMPTY GO GET BYTE
;
;  CURSOR POSITION WAS ZERO...READ A NEW BLOCK INTO
;  THE BUFFER.
;
RDNBLK:	PUSH	D	;BUFFER POINTER
	PUSH	H	;TABLE ADDRESS
	INX	H
	CALL	PHEAD	;PREPARE THE HEADER FOR READ
	CALL	RFBLK	;READ IN THE BLOCK
	JC	TERE2	;ERROR POP OFF STACK BEFORE RETURN
	POP	H
	MOV	A,E	;LOW BYTE OF COUNT (WILL BE ZERO IF 256)
	ORA	D	;SEE IF BOTH ARE ZERO
	JZ	EOFER	;BYTE COUNT WAS ZERO....END OF FILE
	MOV	M,E	;NEW COUNT ( ZERO IS 256 AT THIS POINT)
	INX	H	;BUFFER LOCATION POINTER
	MVI	M,0
	DCX	H
	MOV	A,E	;COUNT TO A
	POP	D	;GET BACK BUFFER ADDRESS
;
;
;
;   THIS ROUTINE GETS ONE BYTE FROM THE BUFFER
;  AND RETURNS IT IN REGISTER "A".  IF THE END
;  OF THE BUFFER IS REACHED IT MOVES THE POINTER
;  TO THE BEGINNING OF THE BUFFER FOR THE NEXT
;  LOAD.
;
GTBYT:	DCR	A	;BUMP THE COUNT
	MOV	M,A	;RESTORE IT
	INX	H
	MOV	A,M	;GET BUFFER POSITION
	INR	M	;BUMP IT
;
	ADD	E
	MOV	E,A	;DE NOW POINT TO CORRECT BUFFER POSITION
	JNC	RT1
	INR	D
RT1:	LDAX	D	;GET CHARACTER FROM BUFFER
	ORA	A	;CLEAR CARRY
	RET		;ALL DONE
;
;
;
;
;      THIS ROUTINE IS USED TO WRITE A BYTE TO THE FILE
;
;      ON ENTRY:   A -  HAS FILE NUMBER
;                  B -  HAS DATA BYTE
;
;
WTBYT:	CALL	LFCB	;GET CONTROL BLOCK
	RZ		;FILE WASN'T OPEN
	INR	A
	RZ		;FILE WAS READ
	MVI	M,0FEH	;SET IT TO WRITE
	INX	H
	INX	H
	MOV	A,B	;GET CHARACTER
	PUSH	PSW
	PUSH	H	;SAVE CONTROL ADDRESS+2
;
;   NOW DO THE WRITE
;
	CALL	PLOAD	;BC GETS HEADER ADDR, DE BUFFER ADDRESS
	POP	H
	MOV	A,M	;COUNT BYTE
	ADD	E
	MOV	E,A
	JNC	WT1
	INR	D
WT1:	POP	PSW	;CHARACTER
	STAX	D	;PUT CHR IN BUFFER
	ORA	A	;CLEAR FLAGS
	INR	M	;INCREMENT THE COUNT
	RNZ		;RETURN IF COUNT DIDN'T ROLL OVER
;
;   THE BUFFER IS FULL. WRITE IT TO TAPE AND RESET
;  CONTROL BLOCK.
;
	CALL	PHEAD	;PREPARE THE HEADER
	JMP	WFBLK	;WRITE IT OUT AND RETURN
;
;
;
;
;  THIS ROUTINE PUTS THE BLOCK SIZE (256) AND BUFFER
;  ADDRESS IN THE FILE HEADER.
;
PHEAD:	CALL	PLOAD	;GET HEADER AND BUFFER ADDRESSES
	PUSH	B	;HEADER ADDRESS
	LXI	H,BLKOF-1	;PSTOR DOES AN INCREMENT
	DAD	B	;HL POINT TO BLOCKSIZE ENTRY
	LXI	B,256
	CALL	PSTOR
	POP	H	;HL RETURN WITH HEADER ADDRESS
	RET
;
;
PSTOR:	INX	H
	MOV	M,C
	INX	H
	MOV	M,B
	INX	H
	MOV	M,E
	INX	H
	MOV	M,D
	RET
;
;
PLOAD:	INX	H
	MOV	C,M
	INX	H
	MOV	B,M
	INX	H
	MOV	E,M
	INX	H
	MOV	D,M
	RET
;
;
;
;
;
;   THIS ROUTINE SETS THE CORRECT UNIT FOR SYSTEM READS
RFBLK:	CALL	GTUNT	;SET UP A=UNIT WITH SPEED
;
;
;
;
;              TAPE READ ROUTINES
;
;     ON-ENTRY:     A HAS UNIT AND SPEED
;                   HL POINT TO HEADER BLOCK
;                   DE HAVE OPTIONAL PUT ADDRESS
;
;     ON EXIT:      CARRY IS SET IF ERROR OCCURED
;                   TAPE UNITS ARE OFF
;
;
RTAPE:	PUSH	D	;SAVE OPTIONAL ADDRESS
	MVI	B,3	;SHORT DELAY
	CALL	TON
	IN	TDATA	;CLEAR THE UART FLAGS
;
PTAP1:	PUSH	H	;HEADER ADDRESS
	CALL	RHEAD	;GO READ HEADER
	POP	H
	JC	TERR	;IF AN ERROR OR ESC WAS RECEIVED
	JNZ	PTAP1	;IF VALID HEADER NOT FOUND
;
;  FOUND A VALID HEADER NOW DO COMPARE
;
	PUSH	H	;GET BACK AND RESAVE ADDRESS
	LXI	D,THEAD
	CALL	DHCMP	;COMPARE DE-HL HEADERS
	POP	H
	JNZ	PTAP1
;
;
	POP	D	;OPTIONAL "PUT" ADDRESS
	MOV	A,D
	ORA	E	;SEE IF DE IS ZERO
	LHLD	BLOCK	;GET BLOCK SIZE
	XCHG		;...TO DE
;  DE HAS HBLOCK....HL HAS USER OPTION
	JNZ	RTAP	;IF DE WAS ZERO GET TAPE LOAD ADDRESS
	LHLD	LOADR	;GET TAPE LOAD ADDRESS
;
;
;     THIS ROUTINE READS "DE" BYTES FROM THE TAPE
;     TO ADDRESS HL.  THE BYTES MUST BE FROM ONE
;     CONTIGUOUS PHYSICAL BLOCK ON THE TAPE.
;
;          HL HAS "PUT" ADDRESS
;          DE HAS SIZE OF TAPE BLOCK
;
RTAP:	PUSH	D	;SAVE SIZE FOR RETURN TO CALLING PROGRAM
;
RTAP2:	EQU	$	;HERE TO LOOP RDING BLKS
	CALL	DCRCT	;DROP COUNT, B=LEN THIS BLK
	JZ	RTOFF	;ZERO=ALL DONE
;
	CALL	RHED1	;READ THAT MANY BYTES
	JC	TERR	;IF ERROR OR ESC
	JZ	RTAP2	;RD OK--READ SOME MORE
;
;  ERROR RETURN
;
TERR:	XRA	A
	STC		;SET ERROR FLAGS
	JMP	RTOF1
;
;
TOFF:	MVI	B,1
	CALL	DELAY
RTOFF:	XRA	A
RTOF1:	OUT	TAPPT
	POP	D	;RETURN BYTE COUNT
	RET
;
;
DCRCT:	EQU	$	;COMMON RTN TO COUNT DOWN BLK LENGTHS
	XRA	A	;CLR FOR LATER TESTS
	MOV	B,A	;SET THIS BLK LEN=256
	ORA	D	;IS AMNT LEFT < 256
	JNZ	DCRC2	;NO--REDUCE AMNT BY 256
	ORA	E	;IS ENTIRE COUNT ZERO
	RZ		;ALL DONE--ZERO=THIS CONDITION
	MOV	B,E	;SET THIS BLK LEN TO AMNT REMAINING
	MOV	E,D	;MAKE ENTIRE COUNT ZERO NOW
	RET		;ALL DONE (NON-ZERO FLAG)
DCRC2:	EQU	$	;REDUCE COUNT BY 256
	DCR	D	;DROP BY 256
	ORA	A	;FORCE NON-ZERO FLAG
	RET		;NON-ZERO=NOT DONE YET (BLK LEN=256)
;
;
;   READ THE HEADER
;
RHEAD:	MVI	B,10	;FIND 10 NULLS
RHEA1:	CALL	STAT
	RC		;IF ESCAPE
	IN	TDATA	;IGNORE ERROR CONDITIONS
	ORA	A	;ZERO?
	JNZ	RHEAD
	DCR	B
	JNZ	RHEA1	;LOOP UNTIL 10 IN A ROW
;
;    WAIT FOR THE START CHARACTER
;
SOHL:	CALL	TAPIN
	RC		;ERROR OR ESCAPE
	CPI	1	;ARE WE AT THE 01 YET (START CHAR)
	JC	SOHL	;NO, BUT STIL ZEROES
	JNZ	RHEAD	;NO, LOOK FOR ANOTHER 10 NULLS
;
;    WE HAVE  10 (OR MORE) NULLS FOLLOWED IMMEDIATELY
;    BY AN 01.  NOW READ THE HEADER.
;
	LXI	H,THEAD	;POINT TO BUFFER
	MVI	B,HLEN	;LENGTH TO READ
;
RHED1:	EQU	$	;RD A BLOCK INTO HL FOR B BYTES
	MVI	C,0	;INIT THE CRC
RHED2:	EQU	$	;LOOP HERE
	CALL	TAPIN	;GET A BYTE
	RC
	MOV	M,A	;STORE IT
	INX	H	;INCREMENT ADDRESS
	CALL	DOCRC	;GO COMPUTE THE CRC
	DCR	B	;WHOLE HEADER YET?
	JNZ	RHED2	;DO ALL THE BYTES
;
;   THIS ROUTINE GETS THE NEXT BYTE AND COMPARES IT
; TO THE VALUE IN REGISTER C.  THE FLAGS ARE SET ON
; RETURN.
;
	CALL	TAPIN	;GET CRC BYTE
	XRA	C	;CLR CARRY AND SET ZERO IF MATCH, ELSE NON-ZERO
	RZ		;CRC IS FINE
	LDA	IGNCR	;BAD CRC, SHD WE STILL ACCEPT IT
	INR	A	;SEE IF IT WAS FF, IF FF THEN ZERO SAYS IGN ERR
;   NOW, CRC ERR DETECTION DEPENDS ON IGNCR.
	RET
;
;    THIS ROUTINE GETS THE NEXT AVAILABLE BYTE FROM THE
;  TAPE.  WHILE WAITING FOR THE BYTE THE KEYBOARD IS TESTED
;  FOR AN ESC COMMAND.  IF RECEIVED THE TAPE LOAD IS
;  TERMINATED AND A RETURN TO THE COMMAND MODE IS MADE.
;
STAT:	IN	TAPPT	;TAPE STATUS PORT
	ANI	TDR
	RNZ
	CALL	SINP	;CHECK INPUT
	JZ	STAT	;NOTHING THERE YET
	ANI	7FH	;CLEAR PARITY 1ST
	JNZ	STAT	;EITHER MODE OR CTL-@
	STC		;SET ERROR FLAG
	RET		;AND RETURN
;
;
;
TAPIN:	CALL	STAT	;WAIT UNTIL A CHARACTER IS AVAILABLE
	RC
;
TREDY:	IN	TAPPT	;TAPE STATUS
	ANI	TFE+TOE	;DATA ERROR?
	IN	TDATA	;GET THE DATA
	RZ		;IF NO ERRORS
	STC		;SET ERROR FLAG
	RET
;
;
;  THIS ROUTINE GETS THE CORRECT UNIT FOR SYSTEM WRITES
WFBLK:	CALL	GTUNT	;SET UP A WITH UNIT AND SPEED
;
;
;
;       WRITE TAPE BLOCK ROUTINE
;
;   ON ENTRY:   A   HAS UNIT AND SPEED
;              HL   HAS POINTER TO HEADER
;
;
WTAPE:	EQU	$	;HERE TO WRITE TAPE
	PUSH	H	;SAVE HEADER ADDRESS
	CALL	WHEAD	;TURN ON, THEN WRITE HDR
	POP	H
	LXI	D,BLKOF	;OFFSET TO BLOCK SIZE IN HEADER
	DAD	D	;HL POINT TO BLOCK SIZE
	MOV	E,M
	INX	H
	MOV	D,M	;DE HAVE SIZE
	INX	H
	MOV	A,M
	INX	H
	MOV	H,M
	MOV	L,A	;HL HAVE STARTING ADDRESS
;
;    THIS ROUTINE WRITES ONE PHYSICAL BLOCK ON THE
;  TAPE "DE" BYTES LONG FROM ADDRESS "HL".
;
;
WTAP1:	EQU	$	;HERE FOR THE EXTRA PUSH
	PUSH	H	;A DUMMY PUSH FOR LATER EXIT
WTAP2:	EQU	$	;LOOP HERE UNTIL ENTIRE AMOUNT READ
	CALL	DCRCT	;DROP COUNT IN DE AND SET UP B W/LEN THIS BLK
	JZ	TOFF	;RETURNS ZERO IF ALL DONE
	CALL	WTBL	;WRITE BLOCK FOR BYTES IN B (256)
	JMP	WTAP2	;LOOP UNTIL ALL DONE
;
;
WRTAP:	PUSH	PSW
WRWAT:	IN	TAPPT	;TAPE STATUS
	ANI	TTBE	;IS TAPE READY FOR A CHAR YET
	JZ	WRWAT	;NO--WAIT
	POP	PSW	;YES--RESTORE CHAR TO OUTPUT
	OUT	TDATA	;SEND CHAR TO TAPE
;
DOCRC:	EQU	$	;A COMMON CRC COMPUTATION ROUTINE
	SUB	C
	MOV	C,A
	XRA	C
	CMA
	SUB	C
	MOV	C,A
	RET		;ONE  BYTE NOW WRITTEN
;
;
;   THIS ROUTINE WRITES THE HEADER POINTED TO BY
;   HL TO THE TAPE.
;
WHEAD:	EQU	$	;HERE TO 1ST TURN ON THE TAPE
	CALL	WTON	;TURN IT ON, THEN WRITE HEADER
	MVI	D,50	;WRITE 50 ZEROS
NULOP:	XRA	A
	CALL	WRTAP
	DCR	D
	JNZ	NULOP
;
	MVI	A,1
	CALL	WRTAP
	MVI	B,HLEN	;LENGTH TO WRITE OUT
;
WTBL:	MVI	C,0	;RESET CRC BYTE
WLOOP:	MOV	A,M	;GET CHARACTER
	CALL	WRTAP	;WRITE IT TO THE TAPE
	DCR	B
	INX	H
	JNZ	WLOOP
	MOV	A,C	;GET CRC
	JMP	WRTAP	;PUT IT ON THE TAPE AND RETURN
;
;
;   THIS ROUTINE COMPARES THE HEADER IN THEAD TO
;   THE USER SUPPLIED HEADER IN ADDRESS HL.
;   ON RETURN IF ZERO IS SET THE TWO NAMES COMPARED
;
DHCMP:	MVI	B,5
DHLOP:	LDAX	D
	CMP	M
	RNZ
	DCR	B
	RZ		;IF ALL FIVE COMPARED
	INX	H
	INX	D
	JMP	DHLOP
;
GTUNT:	EQU	$	;SET A=SPEED + UNIT
	LDA	FNUMF	;GET UNIT
	ORA	A	;SEE WHICH UNIT
	LDA	TSPD	;BUT 1ST GET SPEED
	JNZ	GTUN2	;MAKE IT UNIT TWO
	ADI	TAPE2	;THIS ONCE=UNIT 2, TWICE=UNIT 1
GTUN2:	ADI	TAPE2	;UNIT AND SPEED NOW SET IN A
	RET		;ALL DONE
;
WTON:	MVI	B,4	;SET LOOP DELAY  (BIT LONGER ON A WRITE)
TON:	EQU	$	;HERE TO TURN A TAPE ON THEN DELAY
	OUT	TAPPT	;GET TAPE MOVING, THEN DELAY
;
DELAY:	LXI	D,0
DLOP1:	DCX	D
	MOV	A,D
	ORA	E
	JNZ	DLOP1
	DCR	B
	JNZ	DELAY
	RET
;
;
;**** -- END OF PROGRAM--
;
;
;
;
;    S Y S T E M    E Q U A T E S
;
;
;          VDM PARAMETERS
;
VDMEM:	EQU	0CC00H	;VDM SCREEN MEMORY
;
;
;            KEYBOARD SPECIAL KEY ASSIGNMENTS
;
;  THESE DEFINITIONS ARE DESIGNED TO ALLOW
;  COMPATABILITY WITH SOLOS(TM). THESE ARE THE
;  SAME KEYS WITH BIT 7 (X'80') STRIPPED OFF.
;
DOWN:	EQU	1AH	;CTL Z
UP:	EQU	17H	;CTL W
LEFT:	EQU	01H	;CTL A
RIGHT:	EQU	13H	;CTL S
CLEAR:	EQU	0BH	;CTL K
HOME:	EQU	0EH	;CTL N
MODE:	EQU	00H	;CTL-@
BACKS:	EQU	5FH	;BACKSPACE
LF:	EQU	10
CR:	EQU	13
BLANK:	EQU	' '
SPACE:	EQU	BLANK
CX:	EQU	'X'-40H
ESC:	EQU	1BH
;
;          PORT ASSIGNMENTS
;
STAPT:	EQU	0	;STATUS PORT GENERAL
SDATA:	EQU	1	;SERIAL DATA
PDATA:	EQU	2	;PARALLEL DATA
KDATA:	EQU	3	;KEYBOARD DATA
DSTAT:	EQU	0C8H	;VDM CONTROL PORT
TAPPT:	EQU	0FAH	;TAPE STATUS PORT
TDATA:	EQU	0FBH	;TAPE DATA PORT
SENSE:	EQU	0FFH	;SENSE SWITCHES
;
;
;
;          BIT ASSIGNMENT MASKS
;
SCD:	EQU	1	;SERIAL CARRIER DETECT
SDSR:	EQU	2	;SERIAL DATA SET READY
SPE:	EQU	4	;SERIAL PARITY ERROR
SFE:	EQU	8	;SERIAL FRAMING ERROR
SOE:	EQU	16	;SERIAL OVERRUN ERROR
SCTS:	EQU	32	;SERIAL CLEAR TO SEND
SDR:	EQU	64	;SERIAL DATA READY
STBE:	EQU	128	;SERIAL TRANSMITTER BUFFER EMPTY
;
KDR:	EQU	1	;KEYBOARD DATA READY
PDR:	EQU	2	;PARALLEL DATA READY
PXDR:	EQU	4	;PARALLEL DEVICE READY
TFE:	EQU	8	;TAPE FRAMING ERROR
TOE:	EQU	16	;TAPE OVERFLOW ERROR
TDR:	EQU	64	;TAPE DATA READY
TTBE:	EQU	128	;TAPE TRANSMITTER BUFFER EMPTY
;
SOK:	EQU	1	;SCROLL OK FLAG
;
TAPE1:	EQU	80H	;1=TURN TAPE ONE ON
TAPE2:	EQU	40H	;1=TURN TAPE TWO ON
;
;
;
;
;       S Y S T E M   G L O B A L    A R E A
;
	ORG    START+0800H	;RAM STARTS JUST AFTER ROM
;
SYSRAM:	EQU	$	;START OF SYSTEM RAM
SYSTP:	EQU	SYSRAM+3FFH	;STACK WORKS FM TOP DOWN
;
;
;   PARAMETERS STORED IN RAM
;
UIPRT:	DS	2	;USER DEFINED INPUT RTN IF NON ZERO
UOPRT:	DS	2	;USER DEFINED OUTPUT RTN IF NON ZERO
DFLTS:	DS	2	;DEFAULT PSUEDO I/O PORTS
IPORT:	DS	1	;CRNT INPUT PSUEDO PORT
OPORT:	DS	1	;CRNT OUTPUT PSUEDO PORT
NCHAR:	DS	1	;CURRENT CHARACTER POSITION
LINE:	DS	1	;CURRENT LINE POSITION
BOT:	DS	1	;BEGINNING OF TEXT DISPLACEMENT
SPEED:	DS	1	;SPEED CONTROL BYTE
ESCFL:	DS	1	;ESCAPE FLAG CONTROL BYTE
TSPD:	DS	1	;CURRENT TAPE SPEED
INPTR:	DS	2	;PTR TO NEXT CHAR POSITION IN INLIN
NUCNT:	DS	1	;NUMBER OF NULLS AFTER CRLF
IGNCR:	DS	1	;IGN CRC ERR FLAG, FF=IGN CRC ERRS, ELSE=NORMAL
;
	DS	10	;ROOM FOR FUTURE EXPANSION
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;    T H I S   I S   T H E   H E A D E R   L A Y O U T    *
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;
THEAD:	DS	5	;NAME
	DS	1	;THIS BYTE MUST BE ZERO
HTYPE:	DS	1	;TYPE
BLOCK:	DS	2	;BLOCK SIZE
LOADR:	DS	2	;LOAD ADDRESS
XEQAD:	DS	2	;AUTO EXECUTE ADDRESS
HSPR:	DS	3	;SPARES
;
HLEN:	EQU	$-THEAD	;LENGTH OF HEADER
BLKOF:	EQU	BLOCK-THEAD	;OFFSET TO BLOCK SIZE
DHEAD:	DS	HLEN	;A DUMMY HDR FOR COMPARES WHILE RD'ING
;
;
CUTAB:	DS	6*4	;ROOM FOR UP TO 6 CUSTOM USER COMMANDS
;
;
FNUMF:	DS	1	;FOR CURRENT FILE OPERATIONS
FCBAS:	DS	7	;1ST FILE CONTROL BLOCK
FCBA2:	DS	7	;2ND FILE CONTROL BLOCK
FBUF1:	DS	2*256	;SYSTEM FILE BUFFER BASE
	DS	1	;"BELL" (X'07') FLAGS START OF INPUT BFR
INLIN:	DS	80	;ROOM FOR THE INPUT LINE
USARE:	EQU	$	;START OF USER AREA
;
;   REMEMBER THAT THE STACK WORKS ITS WAY DOWN-FROM
;   THE END OF THIS 1K RAM AREA.
;
; -*-
