;
; extend.asm: v1.0
; usage: APPEND <filename> <string>
;	 appends <string> to end of file <filename>
;
; Intended mainly to illustrate a fast file-append
; algorythm, but may be useful as a quick-update
; notations file handler
;
; 09/13/81 Ron Fowler, Westland MI
;
; CHARACTER EQUATES
;
CR	EQU	13	;CARRIAGE RETURN
LF	EQU	10	;LINEFEED
EOF	EQU	1AH	;END OF FILE
;
; CPM EQUATES
;
CPBASE	EQU	0	;SET TO 4200H FOR ALT CPM
BDOS	EQU	CPBASE+5 ;BDOS ENTRY POINT
PRINTF	EQU	9	;PRINT STRING FUNCTION
OPENF	EQU	15	;OPEN FILE FUNCTION
CLOSEF	EQU	16	;CLOSE FILE FUNCTION
MAKEF	EQU	22	;MAKE NEW FILE FUNCTION
READF	EQU	20	;READ RECORD
WRITEF	EQU	21	;WRITE RECORD
SDMAF	EQU	26	;SET DMA FUNCTION
;
TPA	EQU	CPBASE+100H    ;TRANSIENT PROGRAM AREA
DFCB	EQU	CPBASE+5CH     ;DEFAULT FILE CONTROL BLOCK
DBUF	EQU	CPBASE+80H     ;DEFAULT DISK BUFFER
;
EX	EQU	12	;FCB EXTENT OFFSET
FRC	EQU	15	;RECORD COUNT FCB OFFSET
NR	EQU	32	;NEXT RECORD OFFSET
;
; PROGRAM CODE BEGINS HERE
;
	ORG	TPA
;
BASE:	LXI	H,0	;SAVE THE STACK
	DAD	SP
	SHLD	STACK
	LXI	SP,STACK
	MVI	C,SDMAF ;SET DMA TO DBUF
	LXI	D,DBUF
	CALL	BDOS
	CALL	MOVSTR	;MOVE THE STRING
	CALL	ADVANC	;OPEN AND ADVANCE OUT FILE
	CALL	WRLINE	;WRITE LINE TO OUTPUT FILE
	CALL	CLOSE	;CLOSE OUTPUT FILE
	CALL	ERRXIT	;EXIT WITH MESSAGE
	DB	CR,LF
	DB	'++ Done ++'
	DB	CR,LF,'$'
ERRXIT: POP	D
	MVI	C,PRINTF ;PRINT MESSAGE
	CALL	BDOS
EXIT:	LHLD	STACK	;RESTORE STACKPOINTER
	SPHL
	RET
;
; MOVE STRING INTO BUFFER
;
MOVSTR: LXI	H,DBUF+1 ;POINT TO START OF STRING
	CALL	SCANB	 ;SKIP LEADING BLANKS
	CALL	SCANC	 ;SKIP FILENAME
	CALL	SCANB	 ;SKIP BLANKS AFTER FILENAME
;
; NOW AT STRING, TRANSFER TO BUFFER
;
	XCHG		;CMD LINE PTR IN DE
	LXI	H,LINBUF ;POINT TO BUFFER
MVLN:	MVI	M,EOF	 ;FIRST MARK POSSIBLE END
	LDAX	D	 ;PICK UP BYTE OF LINE
	INX	D
	ORA	A	 ;TERMINATOR?
	RZ
	MOV	M,A	 ;NO, MOVE IT
	INX	H
	JMP	MVLN
;
; SKIP BLANKS
;
SCANB:	MOV	A,M	;GET CHAR
	ORA	A	;TERMINATOR?
	RZ
	CPI	' '	;BLANK?
	RNZ
	INX	H
	JMP	SCANB	;NO,SCAN MORE
;
; SKIP TO FIRST BLANK
;
SCANC:	MOV	A,M	;GET CHAR
	ORA	A	;TERMINATOR?
	RZ
	CPI	' '	;GOT BLANK?
	RZ
	INX    H
	JMP	SCANC	;NO, SCAN MORE
;
; OPEN INPUT FILE AND ADVANCE TO END
;
ADVANC: LXI	D,DFCB
	MVI	C,OPENF ;OPEN IT
	CALL	BDOS
	INR	A	;CHECK RESULT
	JNZ	ADV1	;EXISTS, GO SCAN IT
	MVI	C,MAKEF ;DOESN'T EXIST, CREATE IT
	LXI	D,DFCB
	CALL	BDOS
	INR	A	;TEST RESULT
	JNZ	MAKEOK
	CALL	ERRXIT	;BAD MAKE
	DB	CR,LF
	DB	'++ Can''t create file ++'
	DB	CR,LF,'$'
MAKEOK: MVI	A,80H	;NEW FILE BUF POINTER
	STA	BUFPTR
	RET
ADV1:	LDA	DFCB+FRC ;GET RECORD COUNT
	CPI	80H	;FULL EXTENT?
	JNZ	WIND
	LXI	H,DFCB+EX ;YES, TRY NEXT
	INR	M
	LXI	D,DFCB	;OPEN NEW EXTENT
	MVI	C,OPENF
	CALL	BDOS
	INR	A	;GOOD OPEN?
	JNZ	ADV1	;YES, KEEP SCANNING
BACKEX: LXI	H,DFCB+EX ;NO, RE-OPEN PREV
	DCR	M
	JP	BACKOK	;TEST CASE OF EMPTY FILE
	INR	M	;GET BACK TO EXTENT 0
	JMP	MAKEOK	;AND PRETEND WE MADE IT
BACKOK: MVI	C,OPENF
	LXI	D,DFCB
	CALL	BDOS	;CAN'T GET AN ERROR HERE
	LDA	DFCB+FRC
WIND:	DCR	A	;MAKE ZERO RELATIVE
	JM	BACKEX	;AN EXTENT WITH 0 RECS?
	STA	DFCB+NR  ;SET RECORD TO READ
	MVI	C,READF ;NOW READ IT
	LXI	D,DFCB
	CALL	BDOS	;CAN'T GET ERROR HERE
	LDA	DFCB+NR ;SEE IF NEW EXTENT
	ORA	A
	JNZ	FIXRC	;NO, GO RESET REC CNT
	LXI	H,DFCB+EX ;SIGH...HAVE TO REOPEN PREV
	DCR	M
	LXI	D,DFCB
	MVI	C,OPENF
	CALL	BDOS	;CAN'T GET ERROR HERE
	LDA	DFCB+FRC
FIXRC:	DCR	A	;RESET NEXT REC
	STA	DFCB+NR
;
; NOW SCAN SECTOR IN DBUF FOR EOF MARK
;
	LXI	H,DBUF
	MVI	B,128	;SCAN LIMIT
EOFSC:	MOV	A,M	;PICK UP A CHAR
	CPI	EOF
	JZ	GOTEOF
	INX	H	;NOT YET
	DCR	B
	JNZ	EOFSC	;CONTINUE SCAN
	CALL	ERRXIT	;??? NO EOF IN LAST SECTOR
	DB	CR,LF
	DB	'++ No EOF in last sector ++'
	DB	CR,LF,'$'
GOTEOF: MOV	A,L	;USE LO BYTE FOR BUF PTR
	STA	BUFPTR
	RET
;
; WRITE LINE TO OUTPUT FILE
;
WRLINE: LXI	H,LINBUF
WRLP:	MOV	A,M
	CPI	EOF	;LINE END?
	JZ	WREND
	PUSH	H	;SAVE LINE POINTER
	CALL	WRBYTE	;WRITE IT
	POP	H
	INX	H
	JMP	WRLP
WREND:	MVI	A,CR	;WRITE TERMINATING CR/LF
	CALL	WRBYTE
	MVI	A,LF
;
; WRITE A BYTE TO THE OUTPUT FILE
;
WRBYTE: PUSH	PSW	;SAVE OUTPUT BYTE
	LDA	BUFPTR	;GET BUFFER POINTER
	ORA	A	;CHECK FOR WRAP
	CZ	WRSEC	;WRAPPED, WRITE RECORD
	LXI	H,DBUF	;GET ADDRESS OF DISK BUFFER
	MOV	L,A	;OFFSET TO POINTER
	INR	A	;BUMP POINTER
	STA	BUFPTR
	POP	PSW	;RETRIEVE OUTPUT BYTE
	MOV	M,A	;STORE IT
	RET
;
; WRITE A DISK SECTOR
;
WRSEC:	MVI	C,WRITEF ;BDOS WRITE SEC FUNCTION
	LXI	D,DFCB
	CALL	BDOS
	ORA	A	 ;TEST RESULT
	MVI	A,80H	 ;(LOAD NEW BUF PTR)
	RZ
	CALL	ERRXIT	 ;PRINT DIAGNOSTIC AND EXIT
	DB	CR,LF
	DB	'++ Disk full ++'
	DB	CR,LF,'$'
;
; PAD OUT THE EXISTING BUFFER WITH EOF'S, THEN CLOSE
;
CLOSE:	MVI	A,EOF	;WRITE AN EOF
	CALL	WRBYTE
	LDA	BUFPTR	;CHECK FOR SECTOR PAD
	ORA	A	;SPILLED TO NEXT SECTOR?
	JNZ	CLOSE	;NOT, THEN KEEP PADDING
	MVI	C,WRITEF ;WRITE THE LAST SECTOR
	LXI	D,DFCB
	CALL	BDOS
	MVI	C,CLOSEF
	LXI	D,DFCB	;DONE, CLOSE OUTPUT FILE
	CALL	BDOS
	INR	A	;CHECK RESULT
	RNZ
	CALL	ERRXIT	;PRINT DIAGNOSTIC
	DB	CR,LF
	DB	'++ Can''t close output file ++'
	DB	CR,LF,'$'
;
; DATA AREA
;
LINBUF: DS	130	;LINE BUFFER
BUFPTR: DB	80H	;DISK BUFFER POINTER
	DS	128	;STACK AREA
STACK:	DW	0	;STACKPOINTER SAVE
;
	END	BASE
: DS	130	;LINE BUFFER
BUFPTR: DB	80H	;DISK BUFFER POINTER
	DS	128	;STACK AREA
STACK:	DW	0	;STACKPOINTER SAVE
;
	