;		 SORTV.ASM ver 1.4
;		by Ward Christensen
;		 (revised 6/29/81)
;
;Simple sort program for sorting lists of names,
;or any other variable length file, with CR/LF
;delimited records.
;
;This is a "simple" program: FILE MUST FIT IN MEMORY.
;
;06/29/81 Cleaned up file and re-tabified it.  (KBP)
;
;06/22/81 Changed so MAC is not needed. Changed so alternate or
;	  standard CP/M may be selected. By Ted Shapin.
;
;01/03/81 Change stack init.  By Keith Petersen, W8SDZ
;
;11/15/80 Add @ command (WLC)
;
;10/24/80 Originally written by Ward Christensen
;
;FORMAT:
;	SORTV input-name output-name
; OR	SORTV name
;
;If the second format is used, the file is read into
;memory, sorted, erased, created, and written back.
;
;	The sort will be based on the first characters
;	in the file, unless the command is followed by
;	an "@" sign, then a string (1 or more characters)
;	to skip.  If these are present, the line will be
;	sorted starting after one of these characters.
;
;Example: SORTV NAMES.SUB @.
;
;Will sort NAMES.SUB by filetype, since it skips past
;the "." before doing the compare.
;
EOF	EQU	1AH
CR	EQU	0DH
LF	EQU	0AH
;
BIAS	EQU	0	;0 FOR STANDARD CP/M, 4200H FOR ALTERNATE CP/M
;
;BDOS/CBIOS EQUATES (VERSION 10)
;
RDCON	EQU	1
WRCON	EQU	2
PRINT	EQU	9
RDCONBF EQU	10
CONST	EQU	11
OPEN	EQU	15
CLOSE	EQU	16
SRCHF	EQU	17
SRCHN	EQU	18
ERASE	EQU	19
READ	EQU	20
WRITE	EQU	21
MAKE	EQU	22
REN	EQU	23
STDMA	EQU	26
BDOS	EQU	5+BIAS
FCB	EQU	5CH+BIAS
FCB2	EQU	6CH+BIAS
FCBEXT	EQU	FCB+12
FCBRNO	EQU	FCB+32
;
	ORG	100H+BIAS
;
;INIT LOCAL STACK
;
	LXI	SP,STACK
;
	CALL	START
	DB	'SORTV rev 1.3'
	DB	CR,LF,'$'
;
START	POP	D	;GET ID
	MVI	C,PRINT
	CALL	BDOS	;PRINT ID
;
;START OF PROGRAM EXECUTION
;
	CALL	SVSKIP	;SAVE SKIP INFO
	CALL	CKNAMES ;SEE THAT 2 NAMES ARE THERE
	CALL	OPENIN	;OPEN INPUT FILE
	CALL	READN	;READ THE NAMES
	CALL	SORTN	;SORT THE NAMES
	CALL	WRITEN	;WRITE THE NAMES
	CALL	ERXIT
	DB	'++DONE++$'
;
;====>	SUBROUTINES
;	----------------
;
;====>	SAVE "SKIP TO" INFORMATION
;
SVSKIP	LXI	H,81H+BIAS
;
SVSKL	MOV	A,M
	ORA	A
	RZ		;NO 'SKIP TO'
	CPI	'@'	;SKIP DELIMITER?
	INX	H
	JNZ	SVSKL
	LXI	D,SKIPC ;CHARS TO SKIP
;
SVSKL2	MOV	A,M
	STAX	D
	INX	H
	INX	D
	ORA	A
	JNZ	SVSKL2
	RET
;
;====>	CHECK THAT 2 NAMES WERE SUPPLIED
;
CKNAMES LDA	FCB+1
	CPI	' '
	JZ	NONAME
	LDA	FCB2+1
	CPI	' '
	JZ	SAMENAM
	CPI	'@'	;SKIP PARM?
	JZ	SAMENAM
	LXI	H,FCB2
	LXI	D,OUTNAME
	LXI	B,12
	CALL	MOVER
	RET
;
;OUTPUT NAME = INPUT NAME
;
SAMENAM	LXI	H,FCB
	LXI	D,OUTNAME
	LXI	B,12
	CALL	MOVER
	RET
;
NONAME	CALL	ERXIT
	DB	'++Error - ',CR,LF
	DB	'Command format requires an '
	DB	'input name, and an output name.$'
;
;====>	OPEN THE INPUT FILE
;
OPENIN	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,OPEN
	LXI	D,FCB
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	INR	A
	RNZ		;SUCCESSFUL?  RETURN
	CALL	ERXIT
	DB	'++Input file not found$'
;
;====>	READ IN THE NAMES
;
READN	LXI	H,SBUFF	;TO FIRST NAME
;
READNL	CALL	READL	;READ ONE LINE
	RC		;GOT EOF, RETURN
	CALL	CHAIN	;CHAIN THINGS TOGETHER
	JMP	READNL
;
;====>	READ ONE LINE
;
READL	SHLD	CURR	;SAVE CURR LINE PTR
	XRA	A	;GET 0
	MOV	M,A	;INIT FORWARD
	INX	H	;	POINTER
	MOV	M,A	;	TO
	INX	H	;	0
	LXI	D,SKIPC ;TO CK SKIP CHARS PRESENT
;
READLLP LDA	BDOS+2	;ARE WE
	DCR	A	;	OVER-
	CMP	H	;	FLOW-
	JZ	OFLO	;	ING?
	PUSH	D
	PUSH	H
	LXI	H,EXTFCB
	CALL	RDBYTE	;READ A BYTE
	POP	H
	POP	D
	CPI	EOF	;SET CARRY
	STC		;	AND RETURN
	RZ		;	IF EOF
	MOV	M,A	;STORE CHAR
;TEST FOR SKIP CHAR FOUND
	MOV	B,A	;SAVE FOR COMPARE
	LDAX	D
	ORA	A	;NO MORE SKIP CHARS?
	JZ	READLNS ;NO MORE
	CMP	B	;A SKIP CHAR?
	JNZ	READLNS ;NO, KEEP TRYIN.
	INX	D	;TO NEXT SKIP CHAR
;
READLNS INX	H	;POINT TO NEXT
	MOV	A,B	;GET CHAR
	CPI	CR	;END OF LINE?
	JNZ	READLLP ;	NO, LOOP.
	PUSH	D
	PUSH	H
	LXI	H,EXTFCB
	CALL	RDBYTE	;GOBBLE UP LF
	POP	H
	POP	D
	LDAX	D	;GET SKIP CHAR END
	ORA	A	;TEST IT AND SET "NO EOF"
	RZ
;ERROR - NO SKIP CHAR
	LHLD	CURR
	INX	H	;SKIP
	INX	H	;	POINTER
;
ERPLP	MOV	E,M
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,WRCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	MOV	A,M
	INX	H
	CPI	CR
	JNZ	ERPLP
	CALL	ERXIT
	DB	LF,'++NO SKIP CHAR FOUND++$'
;
OFLO	CALL	ERXIT
	DB	'++File won''t fit in memory$'
;
;====>	CHAIN RECORDS TOGETHER
;
CHAIN	PUSH	H	;SAVE POINTER
	LHLD	CURR	;GET CURRENT
	XCHG		;	TO DE
	LHLD	PREV	;PREV TO HL
	MOV	M,E	;MOVE CURR
	INX	H	;	TO
	MOV	M,D	;	PREV
	XCHG		;THEN MOVE
	SHLD	PREV	;	PREV TO CURR
	POP	H
	RET
;
;====>	SORT THE NAMES
;
SORTN	XRA	A	;SHOW NO
	STA	SWAPS	;	SWAPS
	LXI	H,PTR	;POINT PREV
	SHLD	PREV	;	TO PTR
	LHLD	PTR	;POINT TO FIRST
;
;HANDLE WIERD CASE OF ONLY ONE NAME
;
	MOV	A,M	;GET POINTER
	INX	H	;POINT TO NEXT
	ORA	M	;OR TOGETHER
	DCX	H	;BACK UP
	RZ		;RETURN IF ONLY ONE
;
SORTL	CALL	CMPR	;COMPARE ENTRIES
	CC	SWAP	;SWAP IF WRONG ORDER
	CALL	NEXT	;POINT TO NEXT
	JNC	SORTL	;LOOP IF MORE
	LDA	SWAPS	;ANY
	ORA	A	;	SWAPS?
	JNZ	SORTN	;YES, LOOP
	RET		;NO, RETURN
;
;---->	COMPARE TWO NAMES
;
CMPR	PUSH	H	;SAVE POINTER
	MOV	E,M	;GET NEXT
	INX	H	;	POINTER
	MOV	D,M	;	TO DE
	INX	D	;ALIGN POINTERS
;
;SKIP IF NECESSARY
;
	LXI	B,SKIPC
;
TSTSKIP LDAX	B
	ORA	A
	JZ	COMPL	;NO SKIP
	INX	B
;
SKIP1	INX	H
	CMP	M
	JNZ	SKIP1
	XCHG		;SWAP
;
SKIP2	INX	H
	CMP	M
	JNZ	SKIP2
	XCHG		;PUT THINGS BACK
	JMP	TSTSKIP
;
COMPL	INX	D	;TO NEXT
	INX	H	;TO NEXT
	LDAX	D	;GET ONE
	CMP	M	;COMPARE
	JNZ	COMPNE	;NO COMPARE
	CPI	CR	;END?
	JNZ	COMPL	;	NO, LOOP
;
COMPH	POP	H	;RESTORE POINTER
	RET		;THEY ARE EQUAL
;
;COMPARE NOT EQUAL - SEE IF END OF ELEMENT,
;AND IF SO, CALL THEM EQUAL
;
COMPNE	MOV	A,M
	CPI	CR
	JZ	COMPH
	LDAX	D
	CMP	M
	JMP	COMPH	;CARRY SET AS APPROP
;
;---->	SWAP ENTRIES
;
;LOGIC: PTR POINTS TO SOME ENTRY, WHICH POINTS
;TO ANOTHER ENTRY.  THEY ARE NOT IN ORDER.  THUS:
;POINT PTR TO THE SECOND, POINT THE SECOND TO
;THE FIRST, AND POINT THE FIRST TO WHAT THE
;SECOND USED TO POINT TO.
;
SWAP	MVI	A,1
	STA	SWAPS	;SHOW WE SWAPPED
;BC=NEXT
	MOV	C,M
	INX	H
	MOV	B,M
	DCX	H
;CHAIN CURRENT TO NEXT ONES CHAIN
	LDAX	B
	MOV	M,A
	INX	B
	INX	H
	LDAX	B
	MOV	M,A
	DCX	B
	DCX	H
;SAVE CURRENT POINTER IN DE
	XCHG
;GET POINTER TO PREV
	LHLD	PREV
;POINT PREV TO NEXT
	MOV	M,C
	INX	H
	MOV	M,B
;STORE CURR IN NEXT
	MOV	A,E
	STAX	B
	INX	B
	MOV	A,D
	STAX	B
	DCX	B
;RESTORE CURRENT POINTER
	XCHG
	RET		;CURRENT POINTER IN DE
;
;---->	GET NEXT ETRY, CARRY IF NOT 2 MORE
;
NEXT	SHLD	PREV	;SAVE POINTER
	MOV	E,M
	INX	H
	MOV	D,M
	XCHG		;HL= NEXT
	MOV	A,H	;CARRY ON
	ORA	L	;	IF HL
	STC		;	=
	RZ		;	0
	MOV	A,M	;GET
	INX	H	;SEE IF THERE
	ORA	M	;	IS
	DCX	H	;	ANOTHER
	RNZ		;THERE IS ANOTHER
	STC		;SHOW NOT 2 TO SWAP
	RET
;
;====>	WRITE THE NAMES
;
WRITEN	LXI	H,0	;INIT
	SHLD	EXTFCB+2 ;	EFCB
	XRA	A	;INIT
	STA	FCBEXT	;	THE
	STA	FCBRNO	;	FCB
;RESTORE NAME
	LXI	H,OUTNAME
	LXI	D,FCB
	LXI	B,12
	CALL	MOVER
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,ERASE
	LXI	D,FCB
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,MAKE
	LXI	D,FCB
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	INR	A	;MAKE OK?
	JZ	BADOUT	;	NO, ERROR
	LHLD	PTR	;GET FIRST
;
WNLP	CALL	WRITEL	;WRITE ONE LINE
	JNC	WNLP	;LOOP IF MORE
	MVI	A,EOF	;WRITE EOF CHAR
	PUSH	H
	LXI	H,EXTFCB
	CALL	WRBYTE
	POP	H
	LXI	H,EXTFCB ;FLUSH
	CALL	FLUSH	 ;	 BUFFERS
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,STDMA	;RESET DMA
	LXI	D,80H+BIAS
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,CLOSE
	LXI	D,FCB
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	CALL	ERXIT	;	AND EXIT
	DB	'++DONE++$'
;
WRITEL	PUSH	H	;SAVE POINTER
	INX	H
;
WRLP	INX	H	;TO NEXT CHAR
	MOV	A,M	;GET CHAR
	PUSH	H
	LXI	H,EXTFCB
	CALL	WRBYTE	;WRITE IT
	POP	H
	MOV	A,M	;SEE IF END
	CPI	CR	;	OF LINE
	JNZ	WRLP	;NO, LOOP
	MVI	A,LF	;OTHERWISE
	PUSH	H
	LXI	H,EXTFCB
	CALL	WRBYTE	;WRITE LF
	POP	H
	POP	H	;GET POINTER
	MOV	E,M	;GET
	INX	H	;	FORWARD
	MOV	D,M	;	POINTER
	XCHG		;PUT IT IN HL
	MOV	A,H	;IS POINTER
	ORA	L	;	ZERO?
	RNZ		;NO, RETURN
	STC		;CARRY SHOWS END
	RET
;
BADOUT	CALL	ERXIT
	DB	'++Can''t make output file$'
;
;FOLLOWING FROM 'EQU10.LIB'---->
;
;MOVE, COMPARE SUBROUTINES
;
MOVER	MOV	A,M
	STAX	D
	INX	H
	INX	D
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	MOVER
	RET
;
;	FROM EQU10.LIB: AS OF 07/19/80
;
;RDBYTE, HL POINTS TO EXTENDED FCB:
;
;	2 BYTE BUFFER ADDR
;	2 BYTE "BYTES LEFT" (INIT TO 0)
;	1 BYTE BUFFER SIZE (IN PAGES)
;	2 BYTE FCB ADDRESS
;
RDBYTE	MOV	E,M
	INX	H
	MOV	D,M	;GET BUFFER ADDR
	INX	H
	MOV	C,M
	INX	H
	MOV	B,M	;BC = BYTES LEFT
	MOV	A,B	;GET COUNT
	ORA	C
	JNZ	RDBNORD ;NO READ
;
	INX	H	;TO BUFFER SIZE
	MOV	A,M	;GET COUNT
	ADD	A	;MULTIPLY BY 2
	MOV	B,A	;SECTOR COUNT IN B
	INX	H	;TO FCB
	PUSH	H	;SAVE FCB POINTER
	MOV	A,M	;GET..
	INX	H
	MOV	H,M	;..ADDR..
	MOV	L,A	;..TO HL
;
RDBLP	MVI	A,1AH	;GET EOF CHAR
	STAX	D	;SAVE IN CASE EOF
	PUSH	D	;SAVE DMA ADDR
	PUSH	H	;SAVE FCB ADDR
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,STDMA
	CALL	BDOS	;SET DMA ADDR
	POP	H
	POP	D
	POP	B
	POP	D	;GET FCB
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,READ
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	ORA	A
	POP	H	;HL=DMA, DE=FCB
	JNZ	RDBRET	;GOT EOF
	MOV	A,L
	ADI	80H	;TO NEXT BUFF
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A
	XCHG		;DMA TO DE, FCB TO HL
	DCR	B	;MORE SECTORS?
	JNZ	RDBLP	;YES, MORE
;
RDBRET	POP	H	;GET FCB POINTER
	DCX	H	;TO LENGTH
	MOV	A,M	;GET LENGTH
	DCX	H	;TO COUNT
	MOV	M,A	;SET PAGE COUNT
	DCX	H	;TO LO COUNT
	DCX	H	;TO HI FCB
	DCX	H	;TO EFCB START
	JMP	RDBYTE	;LOOP THRU AGAIN
;
RDBNORD INX	H	;TO LENGTH
	MOV	A,M	;GET LENGTH (PAGES)
	XCHG		;BUFF TO HL
	ADD	H
	MOV	H,A	;HL = END OF BUFF
	MOV	A,L
	SUB	C
	MOV	L,A
	MOV	A,H
	SBB	B
	MOV	H,A	;HL = DATA POINTER
	MOV	A,M	;GET BYTE
	XCHG		;EFCB BACK TO HL
	CPI	1AH	;EOF?
	RZ		;YES, LEAVE POINTERS
	DCX	B	;DECR COUNT
	DCX	H	;"BYTES LEFT"
	MOV	M,B
	DCX	H
	MOV	M,C	;STORE BACK COUNT
	RET
;
;SAMPLE EFCB:
;
;EFCB	DW	BUFF	;BUFFER ADDR
;	DW	0	;BYTES LEFT (OR TITE)
;	DB	20	;BUFFER SIZE (IN PAGES)
;	DW	FCB	;FCB ADDRESS
;
;
;WRBYTE, HL POINTS TO EXTENDED FCB:
;
;	2 BYTE BUFFER ADDR
;	2 BYTE "BYTES LEFT" (INIT TO 0)
;	1 BYTE BUFFER SIZE (IN PAGES)
;	2 BYTE FCB ADDRESS
;
WRBYTE	MOV	E,M
	INX	H
	MOV	D,M	;DE=BUF ADDR
	INX	H
	MOV	C,M
	INX	H
	MOV	B,M	;BC=BYTES IN BUFF
	PUSH	D	;SAVE FCB
	XCHG
	DAD	B	;TO NEXT BYTE
	MOV	M,A	;STORE IT
	INX	B	;ONE MORE
	XCHG
	POP	D
;
;SEE IF BUFFER IS FULL
;
	INX	H	;GET
	MOV	A,M	;	SIZE
	CMP	B	;FULL?
	JNZ	WRBNOWR ;NO WRITE
;
	ADD	A	;MULTIPLY BY 2
	MOV	B,A	;SECTOR COUNT IN B
	INX	H	;TO FCB
	PUSH	H	;SAVE FCB POINTER
	MOV	A,M	;GET..
	INX	H	;..FCB..
	MOV	H,M	;..ADDR..
	MOV	L,A	;..TO HL
;
WRBLP	PUSH	D	;SAVE DMA ADDR
	PUSH	H	;SAVE FCB ADDR
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,STDMA
	CALL	BDOS	;SET DMA ADDR
	POP	H
	POP	D
	POP	B
	POP	D	;GET FCB
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,WRITE
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	ORA	A
	POP	H	;HL=DMA, DE=FCB
	JNZ	WRBERR	;GOT ERR
	MOV	A,L
	ADI	80H	;TO NEXT BUFF
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A
	XCHG		;DMA TO DE, FCB TO HL
	DCR	B	;MORE SECTORS?
	JNZ	WRBLP	;YES, MORE
;
WRBRET	POP	H	;GET FCB POINTER
	DCX	H	;TO LENGTH
	DCX	H	;TO COUNT
	MVI	M,0	;SET 0 TO WRITE
	DCX	H	;TO LO COUNT
	MVI	M,0
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,STDMA
	LXI	D,80H+BIAS
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	RET
;
WRBNOWR DCX	H	;TO LENGTH
	MOV	M,B	;SET NEW LENGTH
	DCX	H
	MOV	M,C
	RET
;
;FLUSH THE EFCB BUFFERS
;
FLUSH	MOV	E,M
	INX	H
	MOV	D,M	;DE=BUF ADDR
	INX	H
	MOV	C,M
	INX	H
	MOV	B,M	;BC=BYTES IN BUFF
	INX	H	;TO COUNT
	MOV	A,B
	ORA	C
	RZ		;NOTHING TO WRITE
	MOV	A,C	;GET LOW COUNT
	ADD	A	;SHIFT HIGH TO CARRY
	MOV	A,B	;GET LOW COUNTAL
	RAL		;MULT BY 2, + CARRY
	INR	A	;FUDGE FOR PARTIAL SECT
	MOV	B,A	;SAVE SECTOR COUNT
	INX	H	;TO FCB
	MOV	A,M
	INX	H
	MOV	H,M
	MOV	L,A	;HL=FCB
;
FLUSHL	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,STDMA
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	XCHG
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,WRITE
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	XCHG
	ORA	A
	JNZ	WRBERR
	PUSH	H
	LXI	H,80H
	DAD	D
	XCHG
	POP	H
	DCR	B
	JNZ	FLUSHL
	XCHG
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,CLOSE
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	INR	A
	RNZ
	CALL	ERXIT
	DB	'++OUTPUT FILE CLOSE ERROR ++$'
;
WRBERR	CALL	ERXIT
	DB	'++OUTPUT FILE WRITE ERROR++$'
;
;EXIT WITH ERROR MESSAGE
;
MSGEXIT EQU	$	;EXIT W/"INFORMATIONAL" MSG
;
ERXIT	POP	D	;GET MSG
	MVI	C,PRINT
	CALL	BDOS
;
;EXIT, RESTORING STACK AND RETURN
;
EXIT	JMP	0+BIAS
;
;====>	START OF WORK AREA
;
EXTFCB	DW	DKBUF
	DW	0
	DB	4
	DW	FCB
PREV	DW	PTR	;POINTER TO PREV POINTER
SKIPC	DB	0	;SKIP CHARS END
	DS	8	;VARIABLE SKIP CHARS
;
	DS	100	;STACK AREA
STACK	EQU	$
;
OUTNAME DS	12	;OUTPUT FILENAME
SWAPS	DS	1
CURR	DS	2
PTR	DS	2	;POINTER TO FIRST NAME
;
	ORG	($+255) AND 0FF00H ;TO PAGE
;
DKBUF	DS	256*4	;4 PAGES OF BUFFER
SBUFF	DS	0	;NAMES READ IN HERE
;
	END
