*  PROGRAM NAME:  LIST
*  AUTHOR:  RICHARD CONN
*  VERSION:  1.0
*  DATE:  10 FEB 81
*  PREVIOUS VERSIONS:  -None-
VERS	equ	10	; Version Number

*
*
*  Section 0:  Introduction to the LIST Program
*



*
*	LIST is a CP/M utility which displays a file on the user console in
* paged mode.  It prints 24 lines of text and pauses; typing a ^C at this point
* returns to CP/M, while any other character continues.  If a line is more than
* LINE$LENGTH character long, it is broken and the line count is incremented
* accordingly.
*

*
*	The structure of the LIST Program is as follows --
*
*  Section	Functions/Routines
*  -------	------------------
*
*     0		Introduction and Documentation
*
*     1		Initialization of the LIST Program
*		Significant Labels are:
*		  OPTION:  Command Line option processing
*		    OPTION$NUMBER:  Set the Line Numbering Flag (/N)
*		  DRIVE:  Extract the disk drive letter from the command line
*
*     2		Mainline of the LIST Program
*		Significant Labels are:
*		  LIST:  Open files, perform tests, etc
*		  LIST1:  Start processing loop
*		  LIST$LOOP:  Main program loop
*
*     3		Support Utilties
*		Significant Routines are:
*		  PRINT$ID:  Print Program ID
*		  TYP$COMP:  Compare the 3-byte file type pted to by HL to
*			the FCB type; return w/Zero Set if match
*		  PRINT$MESSAGE:  Print string pted to by return address;
*			string ends in 0
*		  LOADER:  Copy top of buffer down to front of buffer and
*			load up to 16K into buffer
*		  LOADER1:  Load the buffer pted to by HL for B blocks (max)
*		  CRLF:  Print <CR> <LF> (no regs affected)
*		  CHAR$OUT:  Print char in A; no regs affected
*		  CHAR$IN:  Input char in A; only PSW affected
*		  PRINT$LINE:  Print line pted to by HL on console; process
*			line numbering, tab expansion, line overflow fcts
*		  PRINT$NUMBER:  Print HL as up to 5 decimal digits with
*			leading <SP>; follow by <SP>
*
*      4	Buffers
*

NUMBER$LINES	equ	24	; Number of lines/screen
LINE$LENGTH	equ	77	; Maximum number of characters/line
NUMBER$LENGTH	equ	7	; Number of characters in line number

*
*  DEFINE MISCELLANEOUS CONSTANTS USED IN PROGRAM
*
OPT$CHAR	equ	'/'	; Option character
CR		equ	13	; <CR>
LF		equ	10	; <LF>
BS		equ	8	; <BS>
TAB		equ	9	; <TAB>
BEL		equ	7	; <BEL>
CPM		equ	0	; Warm Boot Address
BUFF		equ	80H	; CP/M Buffer
FCB		equ	5CH	; CP/M FCB
WBADR		equ	1	; CP/M Warm Boot Address
BDOS		equ	5	; CP/M BDOS Entry Point
CTRLC		equ	'C'-'@'	; ^C
CTRLZ		equ	'Z'-'@'	; ^Z

*
*
*  Section 1:  Initialization of LIST Program
*



	ORG	100H

	LXI	H,BUFF	; SCAN FOR OPTION
	MOV	A,M	; GET CHAR COUNT
	ADD	L	; ADD TO HL
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A
	INX	H	; HL PTS TO CHAR AFTER LAST CHAR IN COMMAND LINE
	MVI	M,0	; STORE ENDING 0
	LXI	H,BUFF+1	; PT TO 1ST CHAR
	PUSH	H	; SAVE PTR FOR LATER
	XRA	A	; A=0
	STA	NFLG	; TURN OFF LINE NUMBERING FLAG
	STA	OVFL	; TURN OFF LINE LENGTH OVERFLOW FLAG
OPTION:
	MOV	A,M	; SCAN FOR OPTION
	ORA	A	; DONE?
	JZ	OPTION$DONE
	INX	H	; PT TO NEXT
	CPI	OPT$CHAR	; OPTION?
	JNZ	OPTION
	MOV	A,M	; GET OPTION LETTER
	CPI	'N'	; NUMBER LINES?
	JZ	OPTION$NUMBER
	CALL	PRINT$ID	; PRINT PROGRAM ID
	CALL	PRINT$MESSAGE
	DB	CR,LF,'	The LIST command takes the following format --'
	DB	CR,LF,'		LIST d:filename.typ [/N]'
	DB	CR,LF,'	Only the "/N" option is available; this option prints'
	DB	CR,LF,'line numbers in front of each line.'
	DB	CR,LF,'	The drive specification "d:" is optional.',0
	JMP	CPM
*  THIS OPTION TURNS ON THE LINE NUMBERING FLAG
OPTION$NUMBER:
	MVI	A,0FFH	; TURN ON FLAG
	STA	NFLG
	JMP	OPTION
*  DONE PROCESSING OPTIONS
OPTION$DONE:
	POP	H	; GET PTR TO COMMAND LINE
DRIVE:
	MOV	A,M	; SCAN FOR DRIVE NAME
	ORA	A	; DONE?
	JZ	LIST
	INX	H	; PT TO NEXT CHAR
	CPI	' '	; NON-SPACE?
	JZ	DRIVE	; CONTINUE SCAN IF SO
	MOV	A,M	; CHECK FOR COLON
	CPI	':'	; COLON MEANS DRIVE NAME PRECEEDS
	JNZ	LIST
	DCX	H	; PT TO DRIVE NAME
	MOV	A,M	; GET IT
	SUI	'A'	; ADJUST FROM LETTER TO NUMBER (A=0,B=1,ETC)
	JC	DRIVE$ERROR
	CPI	16	; IN RANGE?
	JNC	DRIVE$ERROR
	MOV	E,A	; PLACE NUMBER IN E
	MVI	C,14	; SELECT DISK
	CALL	BDOS
	JMP	LIST
DRIVE$ERROR:
	CALL	PRINT$MESSAGE
	DB	CR,LF,'ERROR:  Invalid Drive Specification',0
	JMP	CPM

*
*
*  Section 2:  Mainline of LIST Program
*



LIST:
	CALL	PRINT$ID	; PRINT PROGRAM ID
	LXI	H,COM$TYPE	; DON'T PRINT *.COM OR *.OBJ FILES
	CALL	TYP$COMP	; COMPARE TYPES
	JZ	TYPE$ERROR
	LXI	H,OBJ$TYPE
	CALL	TYP$COMP
	JNZ	LIST0
TYPE$ERROR:
	CALL	PRINT$MESSAGE
	DB	CR,LF,'ERROR:  Attempt to List COM or OBJ File',0
	JMP	CPM
LIST0:
	LXI	D,FCB		; TRY TO OPEN FILE
	MVI	C,15		; OPEN FILE
	CALL	BDOS
	CPI	0FFH	; ERROR?
	JNZ	LIST1
	CALL	PRINT$MESSAGE
	DB	CR,LF,'ERROR:  File Not Found',0
	JMP	CPM

LIST1:
	CALL	CRLF		; NEW LINE
	LXI	H,BUFFER	; FILE BUFFER (16K)
	MVI	B,8*16		; LOAD 16K OF FILE (MAX)
	CALL	LOADER1		; LOAD BUFFER PTED TO BY HL FOR 16K
	LXI	H,BUFFER	; PT TO FIRST CHARACTER
	LDA	NFLG		; NUMBER LINES?
	ORA	A		; SET FLAGS
	JZ	LIST$LOOP
	PUSH	H		; SAVE HL
	LXI	H,0		; SET FOR 1ST LINE NUMBER
	SHLD	LINE$NUMBER
	POP	H		; RESTORE HL

*
*  MAIN LOOP FOR PRINTING LINES
*
LIST$LOOP:
	MVI	C,NUMBER$LINES	; NUMBER OF LINES/SCREEN
LIST$LOOP1:
	CALL	PRINT$LINE	; PRINT ONE LINE
	CPI	CTRLZ
	JZ	CPM
	CPI	LF		; LINE FEED?
	JNZ	LIST$LOOP2
	INX	H		; PT TO CHAR AFTER <LF>
	MOV	A,M		; GET POSSIBLE ^Z
	CPI	CTRLZ		; ^Z IF SO
	JZ	CPM
LIST$LOOP2:
	DCR	C		; COUNT DOWN
	JNZ	LIST$LOOP3
	CALL	CHAR$IN		; WAIT FOR CHAR
	CALL	CRLF		; NEW LINE
	CPI	CTRLC		; ^C?
	JZ	CPM		; ABORT IF SO
	JMP	LIST$LOOP	; CONTINUE
LIST$LOOP3:
	CALL	CRLF		; NEW LINE
	JMP	LIST$LOOP1

*
*
*  Section 3:  Support Utilities
*



*
*  THIS ROUTINE PRINTS THE PROGRAM ID
*
PRINT$ID:
	CALL	PRINT$MESSAGE
	DB	'LIST  Version ',VERS/10+'0','.',(VERS MOD 10)+'0',0
	RET

*
*  THIS ROUTINE COMPARES THE THREE BYTES PTED TO BY HL AGAINST THE TYPE
*    IN THE FCB; RETURNS W/ZERO SET IF MATCH
*
TYP$COMP:
	LXI	D,FCB+9	; PT TO TYPE IN FCB
	MVI	B,3		; 3 BYTES
TYP$COMP$LOOP:
	LDAX	D		; GET BYTE
	CMP	M		; COMPARE
	RNZ
	INX	H		; PT TO NEXT
	INX	D
	DCR	B
	JNZ	TYP$COMP$LOOP
	RET

*
*  PRINT MESSAGE PTED TO BY RETURN ADDRESS ENDING IN 0
*
PRINT$MESSAGE:
	XTHL			; SAVE HL AND GET PTR
PRINT$MESSAGE$LOOP:
	MOV	A,M		; GET CHAR
	INX	H		; PT TO NEXT
	ORA	A		; DONE?
	JZ	PRINT$MESSAGE$DONE
	CALL	CHAR$OUT	; PRINT IT
	JMP	PRINT$MESSAGE$LOOP
PRINT$MESSAGE$DONE:
	XTHL			; RESTORE HL AND RETURN ADDRESS
	RET

*
*  THIS ROUTINE LOADS THE FILE BUFFER
*	ENTRY POINT 'LOADER' COPIES THE END OF THE FILE BUFFER DOWN TO THE
* BEGINNING AND LOADS THE REST OF THE BUFFER (UP TO 16K).
*	ENTRY POINT 'LOADER1' LOADS THE BUFFER POINTED TO BY HL FOR B-BLOCKS
* (1 BLOCK = 128 BYTES).
*
LOADER:
	PUSH	B		; SAVE BC
	LXI	H,BUFFER$LAST	; PT TO LAST BLOCK
	LXI	D,BUFFER	; PT TO 1ST BLOCK
	CALL	MOVE$BLOCK	; MOVE BLOCK FROM HL TO DE
	CALL	MOVE$BLOCK	; MOVE PAGE (2 BLOCKS)
	XCHG			; PT TO 2ND BLOCK
	MVI	B,8*16-2	; 2 BLOCKS LESS THAN 16K
	CALL	LOADER1		; LOAD BLOCKS
	POP	B		; RESTORE BC
	RET
LOADER1:
	CALL	LOADER2		; LOAD NEXT BLOCK
	RNZ			; DONE IF PAST EOF
	LXI	D,BUFF		; PT TO BUFFER
	XCHG			; EXCHANGE PTRS
	CALL	MOVE$BLOCK	; MOVE BLOCK LOADED INTO FILE BUFFER
	XCHG			; RESTORE PTRS
	DCR	B		; COUNT DOWN
	JNZ	LOADER1
	RET
*
*  LOAD BUFFER FROM DISK
*
LOADER2:
	PUSH H ! PUSH D ! PUSH B
	LXI	D,FCB		; PT TO FILE NAME
	MVI	C,20		; READ BLOCK
	CALL	BDOS
	ORA	A		; SET FLAG
	POP B ! POP D ! POP H
	RET

*
*  MOVE BLOCK (128 BYTES) FROM HL TO DE
*
MOVE$BLOCK:
	PUSH	B		; SAVE BC
	MVI	B,128		; 128 BYTES
MOVE$BLOCK$LOOP:
	MOV	A,M		; GET BYTE
	STAX	D		; PUT BYTE
	INX	H		; PT TO NEXT
	INX	D
	DCR	B		; COUNT DOWN
	JNZ	MOVE$BLOCK$LOOP
	POP	B		; RESTORE BC
	RET

*
*  OUTPUT <CR> <LF>; DON'T CHANGE A
*
CRLF:
	PUSH	PSW	; SAVE A
	MVI	A,CR	; <CR>
	CALL	CHAR$OUT
	MVI	A,LF	; <LF>
	CALL	CHAR$OUT
	POP	PSW	; GET A
	RET

*
*  CHARACTER OUTPUT ROUTINE
*    OUTPUT CHARACTER IN REG A TO CONSOLE
*
CHAR$OUT:
	PUSH H ! PUSH D ! PUSH B ! PUSH PSW
	MOV	E,A		; CHAR IN E
	MVI	C,2		; OUTPUT TO CON:
	CALL	BDOS
	POP PSW ! POP B ! POP D ! POP H
	RET

*
*  CHARACTER INPUT ROUTINE
*    CHARACTER IS RETURNED IN REG A
*
CHAR$IN:
	PUSH H ! PUSH D ! PUSH B
	LXI	H,CHAR$IN$RET	; PLACE RETURN ADDRESS ON STACK
	PUSH	H
	LHLD	WBADR		; INDEX INTO BIOS FOR NO ECHO
	MOV	A,L		; ADD 6 FOR CONSOLE INPUT ROUTINE
	ADI	6
	MOV	L,A
	MOV	A,H
	ACI	0
	MOV	H,A		; HL PTS TO ROUTINE
	PCHL			; "CALL" CONSOLE INPUT ROUTINE
CHAR$IN$RET:
	POP B ! POP D ! POP H
	RET

*
*  PRINT LINE PTED TO BY HL ON CONSOLE
*
PRINT$LINE:
	PUSH	B		; SAVE LINE COUNT
	MVI	C,0		; SET CHAR COUNT
	LDA	NFLG		; NUMBER LINE?
	ORA	A		; 0=NO
	JZ	PRINT$LINE1
	LDA	OVFL		; OVERFLOW FROM PREVIOUS LINE?
	ORA	A		; 0=NO
	JNZ	PRINT$LINE1
	PUSH	H		; SAVE PTR TO LINE
	LHLD	LINE$NUMBER	; FETCH AND INCREMENT LINE NUMBER
	INX	H
	SHLD	LINE$NUMBER
	CALL	PRINT$NUMBER	; PRINT LINE NUMBER
	POP	H		; RESTORE PTR TO LINE
PRINT$LINE1:
	XRA	A		; TURN OFF OVERFLOW FLAG
	STA	OVFL
	LXI	D,BUFFER$LAST	; IN LAST BLOCK?
	MOV	A,H		; CHECK AGAINST H
	CMP	D 
	JNZ	PRINT$LINE$LOOP
	PUSH	H		; SAVE PTR TO LINE (RELATIVE OFFSET IN L)
	CALL	LOADER		; LOAD NEXT 16K
	LXI	H,BUFFER	; PT TO 1ST BYTE OF BLOCK
	POP	D		; GET RELATIVE OFFSET IN E
	MOV	L,E		; RELATIVE OFFSET IN L -- CONTINUE
PRINT$LINE$LOOP:
	MOV	A,M		; GET CHAR FROM FILE
	INX	H		; PT TO NEXT CHAR
	ANI	7FH		; MASK OUT MSB
	CPI	CTRLZ		; PROCESS EOF
	JZ	PRINT$LINE$CR
	CPI	BS		; PROCESS <BS>
	JZ	PRINT$LINE$BS
	CPI	TAB		; PRINT <TAB>
	JZ	PRINT$LINE$TAB
	CPI	CR		; PROCESS EOL
	JZ	PRINT$LINE$CR
	CPI	' '		; DON'T OUTPUT LESS THAN <SP>
	JC	PRINT$LINE$LOOP
	CPI	7EH		; DON'T OUTPUT IF GREATER THAN OR EQUAL TO 7EH
	JNC	PRINT$LINE$LOOP
	CALL	CHAR$OUT	; PRINT CHAR
	INR	C		; INCREMENT CHAR COUNT
	CALL	OVFL$TEST	; CHECK FOR LINE OVERFLOW
	JMP	PRINT$LINE$LOOP
PRINT$LINE$BS:
	MOV	A,C		; POSSIBLE TO <BS>?
	ORA	A		; 0=NO
	JZ	PRINT$LINE$LOOP
	MVI	A,BS		; PRINT <BS>
	CALL	CHAR$OUT
	DCR	C		; COUNT DOWN
	JMP	PRINT$LINE$LOOP
PRINT$LINE$TAB:
	MVI	A,' '		; PRINT <SP>
	CALL	CHAR$OUT
	INR	C		; INCREMENT COUNT
	CALL	OVFL$TEST	; CHECK FOR LINE OVERFLOW
	MOV	A,C		; MULTIPLE OF 8?
	ANI	7		; MASK FOR 3 LSB
	JNZ	PRINT$LINE$TAB
	JMP	PRINT$LINE$LOOP
PRINT$LINE$CR:
	MOV	A,M		; GET POSSIBLE <LF>
	POP	B		; RESTORE LINE COUNT
	RET

*
*  TEST FOR LINE OVERFLOW AND PRINT OVERFLOW CHARS IF SO
*
OVFL$TEST:
	LDA	NFLG		; NUMBERING LINES?
	ORA	A		; 0=NO
	JZ	OVFL$TEST1
	LDA	OVFL		; IN OVERFLOW?
	ORA	A		; 0=NO
	JNZ	OVFL$TEST1
*  WE ARE ON A NUMBERED LINE
	MVI	A,LINE$LENGTH	; GET LINE LENGTH
	SUI	NUMBER$LENGTH	; SUBTRACT LENGTH OF LEADING NUMBER
	JMP	OVFL$TEST2
*  WE ARE NOT ON A NUMBERED LINE
OVFL$TEST1:
	MVI	A,LINE$LENGTH	; CHECK CHAR COUNT
OVFL$TEST2:
	CMP	C		; OK?
	RNZ
*  CHECK TO SEE IF ONE OF NEXT 3 CHARS IS A <CR>
	MOV	A,M		; NEXT CHAR A <CR>?
	ANI	7FH		; MASK OUT MSB
	CPI	CR
	RZ
	INX	H		; NEXT CHAR A <CR>?
	MOV	A,M
	ANI	7FH
	DCX	H
	CPI	CR
	RZ
	INX	H		; 3RD CHAR A <CR>?
	INX	H
	MOV	A,M
	ANI	7FH
	DCX	H
	DCX	H
	CPI	CR
	RZ
*  NONE OF THEM ARE, SO OVERFLOW
	MVI	A,0FFH		; SET OVERFLOW FLAG
	STA	OVFL
	MVI	A,' '		; PRINT OVERFLOW CHARS
	CALL	CHAR$OUT
	MVI	A,'<'
	CALL	CHAR$OUT
	POP	D		; CLEAR STACK
	MVI	A,LF		; FAKE A <LF>
	DCX	H		; BACK UP IN PREPARATION FOR <LF> ADVANCE
	POP	B		; RESTORE LINE COUNT
	RET			; RETURN TO MAIN LOOP

*
*  PRINT NUMBER IN HL AS UP TO 5 DECIMAL DIGITS FOLLOWED BY A <SP>
*    AFFECT NO REGISTERS
*
PRINT$NUMBER:
	PUSH H ! PUSH D ! PUSH B ! PUSH PSW
	MVI	A,0FFH 
	STA	LDSP	; TURN OFF LEADING <SP> FLAG
	LXI	D,10000	; DETERMINE 10,000'S COUNT
	CALL	PNUM
	LXI	D,1000	; DETERMINE 1,000'S COUNT
	CALL	PNUM
	LXI	D,100	; 100'S
	CALL	PNUM
	LXI	D,10	; 10'S
	CALL	PNUM
	MOV	A,L	; 1'S
	ADI	'0'	; CONVERT TO ASCII
	CALL	CHAR$OUT	; PRINT IT
	MVI	A,':'	; PRINT COLON
	CALL	CHAR$OUT
	MVI	A,' '	; PRINT <SP>
	CALL	CHAR$OUT
	POP PSW ! POP B ! POP D ! POP H
	RET
*
*  PNUM IS A UTILITY TO SUBTRACT DE FROM HL UNTIL HL<0; PRINT NUMBER OF TIMES
*    SUBTRACTION WAS DONE; IF ZERO AND LEADING SPACE FLAG SET, PRINT <SP>;
*    ELSE, PRINT DIGIT AND CLEAR LEADING SPACE FLAG
*    ON EXIT, HL=HL-DE
*
PNUM:
	MVI	C,'0'		; SET DIGIT
PNUM1:
	MOV	A,L		; GET NUMBER
	SUB	E
	MOV	L,A
	MOV	A,H
	SBB	D
	MOV	H,A		; HL=HL-DE
	JC	PNUM2
	INR	C		; INCREMENT COUNT
	JMP	PNUM1
PNUM2:
	MOV	A,L		; ADD BACK IN SO HL IS AGAIN 0 OR POS
	ADD	E
	MOV	L,A
	MOV	A,H
	ADC	D
	MOV	H,A		; HL RESTORED
	MOV	A,C		; GET DIGIT
	CPI	'0'		; ZERO?
	JNZ	PNUM3
	LDA	LDSP		; LEADING <SP>?
	ORA	A		; 0=NO
	JZ	PNUM3
	MVI	A,' '		; PRINT <SP>
	JMP	PNUM4
PNUM3:
	XRA	A		; NO LEADING <SP>
	STA	LDSP		; TURN OFF FLAG
	MOV	A,C		; GET CHAR
PNUM4:
	CALL	CHAR$OUT
	RET

*
*
*  Section 4:  Buffers
*

COM$TYPE:
	DB	'COM'		; FOR COMPARISON AGAINST COM FILE TYPE
OBJ$TYPE:
	DB	'OBJ'		; FOR COMPARISON AGAINST OBJ FILE TYPE
LDSP:
	DS	1		; LEADING <SP> FLAG (0=NO)
OVFL:
	DS	1		; LINE LENGTH OVERFLOW FLAG (0=NO)
NFLG:
	DS	1		; LINE NUMBER FLAG (0=NO)
LINE$NUMBER:
	DS	2		; LINE NUMBER STORAGE BUFFER


	DS	60		; STACK SPACE
STACK	EQU	$

	ORG	$/256*256+256
BUFFER:
	DS	128*(16*8-2)	; FILE BUFFER SPACE
BUFFER$LAST:
	DS	128*2		; LAST BLOCK

	END
