;	THIS PROGRAM PRINTS THE LAST FEW LINES OF A TEXT FILE.
;
;	USAGE:	TAIL -n filename
;		-n is optional, limits output to n lines (20 default)
;
;	BY JON DART, DEPT. OF ANTHROPOLOGY, UCSD C-001, LA JOLLA, CA 92093
;
;	VERSION 1.1: 15-MAR-85
;	VERSION 1.0: 15-JAN-85
;
BUFSIZ	EQU	64		;SIZE OF BUFFER IN RECORDS

;	CP/M SYSTEM DEFINITIONS:

BOOT	EQU	0
BDOS	EQU	0005H
DEFFCB	EQU	005CH
DEFBUF	EQU	0080H
FCBSIZ	EQU	36
SECSIZ	EQU	128
R0	EQU	33
R2	EQU	35

CONOUT	EQU	2
PRTSTR	EQU	09H
VERS	EQU	0CH
FOPEN	EQU	0FH
FCLOSE	EQU	10H
SETDMA	EQU	1AH
RDRAND	EQU	21H
CFS	EQU	23H

;	ASCII CHARACTERS:

LF	EQU	0AH
CR	EQU	0DH
TAB	EQU	09H
SPACE	EQU	20H
CTRL$Z	EQU	26

	ORG	100H

TAIL	EQU	$
	LXI	H,0
	DAD	SP
	SHLD	OLDSTK		;SAVE OLD STACK POINTER
	LXI	SP,STACK
	CALL	CRLF
	MVI	C,VERS
	CALL	BDOS
	MOV	A,L
	CPI	20H
	JNC	VERSOK		;IF NOT CP/M 2.2
	LXI	D,MSG0
	MVI	C,PRTSTR
	CALL	BDOS
	JMP	EXIT2
VERSOK: LXI	H,FCB
	MVI	B,FCBSIZ
	CALL	ZERO		;ZERO FCB
	LXI	H,DEFBUF	;GET BYTE COUNT FOR COMMAND LINE
	MOV	A,M
	ANA	A
	JZ	NONAME		;IF NO NAME
	PUSH	H
	MOV	E,M
	MVI	D,0
	DAD	D
	INX	H
	MVI	M,0		;PUT 0 BYTE AT END OF COMMAND LINE
	POP	H
	INX	H
	CALL	SKIPS		;SKIP SPACES
	JC	NONAME		;IF END OF LINE
	CPI	'-'
	JNZ	NONUM		;IF NO NUMBER SPECIFIED
	INX	H
	MVI	C,0
	PUSH	H
CD:	MOV	A,M		;HAVE NUMBER, COUNT NUMBER OF DIGITS
	CPI	'0'
	JC	NODGT
	CPI	'9'+1
	JNC	NODGT
	INX	H
	INR	C
	JMP	CD
NODGT:	SHLD	NUMEND		;SAVE ADDRESS AFTER NUMBER
	POP	H
	CALL	DTOBIN		;CONVERT NUMBER TO BINARY
	JC	NONAME		;IF ERROR
	XCHG
	INX	H
	SHLD	NUMLIN		;STORE AS # LINES
	LHLD	NUMEND		;POINT TO 1 PAST NUMBER
	CALL	SKIPS
	JC	NONAME
NONUM:	LXI	D,FCB
	CALL	PACK		;MOVE NAME INTO FCB
	JMP	NAMEOK
NONAME: LXI	D,MSG1
	MVI	C,PRTSTR
	CALL	BDOS
	JMP	EXIT2
NAMEOK:
	LXI	D,FCB
	MVI	C,FOPEN
	PUSH	D
	CALL	BDOS		;TRY TO OPEN FILE
	POP	D
	CPI	0FFH
	JNZ	OPENOK		;IF OK
BADOPN: XCHG
	LXI	D,FILNAM
	CALL	FCOPY		;COPY FILE NAME INTO MESSAGE
	LXI	D,MSG2
	MVI	C,PRTSTR
	CALL	BDOS		;PRINT MESSAGE
	JMP	EXIT

SKIPS1: INX	H
SKIPS:	MOV	A,M
	ANA	A
	STC
	RZ
	CPI	SPACE
	JZ	SKIPS1
	CPI	TAB
	JZ	SKIPS1
	ANA	A
	RET

;	COPY FILE NAME FROM COMMAND LINE TO FCB
;	ENTRY: (HL) POINTS TO FILE NAME
;	       (DE) POINTS TO FCB
;
PACK:	INX	H
	MOV	A,M
	CPI	':'
	DCX	H
	JZ	HAVED		;IF DRIVE SPECIFIED
	XRA	A
	STAX	D		;ELSE PUT 0 IN 1ST BYTE
	JMP	PACK2
HAVED:	MOV	A,M
	SUI	'A'-1
	STAX	D
	INX	H
	INX	H
PACK2:	INX	D
	PUSH	H
	PUSH	D
	XCHG
	MVI	B,11
	MVI	A,SPACE
	CALL	FILL		;FILL NAME FIELD WITH SPACES
	POP	D
	POP	H
	MVI	C,8
PACK3:	MOV	A,M		;COPY NAME
	ANA	A
	RZ
	CPI	SPACE
	RZ
	INX	H
	CPI	'.'
	JZ	PACK4
	STAX	D
	INX	D
	DCR	C
	JNZ	PACK3
	MOV	A,M						;15-MAR-85
	ANA	A						;15-MAR-85
	RZ							;15-MAR-85
	INX	H		;FIXES BUG			;15-MAR-85
	JMP	PACK5
PACK4:	XCHG
	MOV	B,C
	MVI	A,SPACE
	CALL	FILL
	XCHG
PACK5:	MVI	C,3		;COPY EXTENSION
PACK6:	MOV	A,M
	ANA	A
	RZ
	CPI	SPACE
	RZ
	STAX	D
	INX	H
	INX	D
	DCR	C
	JNZ	PACK6
	RET

ZERO:	XRA	A
FILL:	MOV	M,A
	INX	H
	DCR	B
	JNZ	FILL
	RET 

OPENOK	EQU	$
	LXI	D,FCB
	MVI	C,CFS
	CALL	BDOS		;COMPUTE FILE SIZE
	LHLD	FCB+R0		;GET SIZE
	MOV	A,L
	ORA	H
	JZ	EXIT		;IF 0
	SHLD	FILSIZ		;SAVE SIZE
	LXI	D,BUFSIZ
	CALL	SUBHLDE		;SUBTRACT BUFFER SIZE
	JC	SMALL		;IF FILE SIZE < BUFFER SIZE
	PUSH	H
	LXI	H,BUFSIZ	;SET BUFFER SIZE
	SHLD	NREAD		;AS NUMBER OF RECORDS TO READ
	POP	H
	JMP	GOTSIZ
SMALL:	LHLD	FILSIZ		;SET FILE SIZE
	SHLD	NREAD		;AS NUMBER OF RECORDS TO READ
	LXI	H,0		;START FROM RECORD 0
GOTSIZ: SHLD	FCB+R0		;SET RANDOM RECORD BYTES
	CALL	FILBUF		;FILL BUFFER FROM DISK
	LHLD	NREAD
	DCX	H
	PUSH	H
	POP	B
	LXI	D,SECSIZ
	LXI	H,BUFFER
MLOOP:	MOV	A,B		;COMPUTE ADDRESS OF START OF LAST SECTOR
	ORA	C
	JZ	MDONE
	DAD	D
	DCX	B
	JMP	MLOOP
MDONE:	MVI	C,SECSIZ
FCZ:	MOV	A,M		;SEARCH FOR ^Z
	CPI	CTRL$Z
	JZ	FOUND
	INX	H
	DCR	C
	JNZ	FCZ
FOUND:	SHLD	ENDTXT		;SAVE END OF TEXT ADDRESS
	LXI	D,BUFFER
	DCX	H		;POINT TO CHAR. BEFORE ^Z
	PUSH	H
	LHLD	NUMLIN
	PUSH	H
	POP	B		;LINE COUNT IN (BC)
	POP	H
SRC:	CALL	COMPRP
	JZ	BEGIN		;IF REACHED START OF BUFFER
	MOV	A,M		;GET BYTE
	DCX	H
	CPI	LF
	JZ	GOTLF		;IF LF
	CPI	CR
	JNZ	SRC		;IF NOT CR
GOTCR:	DCX	B		;FOUND CR, COUNT A LINE
	MOV	A,M		;GET PREVIOUS CHAR.
	CPI	LF		;IF LF,
	JNZ	NOSKIP
	DCX	H		;SKIP IT
NOSKIP: MOV	A,B		;GET LINE COUNT
	ORA	C		;MORE TO GO?
	JNZ	SRC		;YES, LOOP
	INX	H		;START OF RIGHT LINE FOUND
	JMP	BEGIN		;GO PRINT
GOTLF:	DCX	B		;COME HERE IF FOUND LF
	MOV	A,M		;IF PREVIOUS CHAR. = CR
	CPI	CR
	JNZ	NOSKIP
	DCX	H		;SKIP IT
	JMP	NOSKIP
BEGIN:	PUSH	H
	LHLD	ENDTXT
	XCHG			;PUT END OF BUFFER ADDR. IN (DE)
	POP	H
SHOW:	CALL	COMPRP		;DISPLAY TEXT
	JZ	EXIT		;UNTIL END OF BUFFER
	MOV	A,M
	CALL	COUT
	INX	H
	JMP	SHOW

;	FILBUF - FILL BUFFER FROM FILE
;
;	ENTRY:	NONE
;
;	EXIT:	'C'=1 IF ERROR
;
FILBUF	EQU	$
	LXI	D,BUFFER	;BUFFER ADDRESS IN (DE)
	LHLD	NREAD
	PUSH	H
	POP	B		;(BC) = COUNT OF SECTORS TO READ
READ11: PUSH	D
	PUSH	B
	MVI	C,SETDMA
	CALL	BDOS		;SET DMA ADDRESS
	LXI	D,FCB
	MVI	C,RDRAND
	CALL	BDOS		;READ A SECTOR
	ANA	A
	POP	B
	POP	D
	JNZ	READ12		;QUIT IF END OF FILE

	DCX	B		;COUNT # SECTORS READ
	XCHG
	LXI	D,SECSIZ
	DAD	D		;UPDATE BUFFER ADDRESS
	XCHG
	LHLD	FCB+R0
	INX	H		;ADVANCE FCB
	SHLD	FCB+R0		;TO NEXT SECTOR
	MOV	A,C		;GET # SECTORS TO READ
	ORA	B
	JNZ	READ11		;LOOP TILL DONE

READ12: ANA	A		;CLEAR CARRY
	RET

EXIT:	LXI	D,FCB		;CLOSE FILE
	MVI	C,FCLOSE
	CALL	BDOS
EXIT2:	LHLD	OLDSTK
	SPHL
	RET			;RETURN TO CCP

FCOPY:	MOV	A,M
	INX	H
	ANA	A
	JZ	NODRV
	ADI	'A'-1
	STAX	D
	INX	D
	MVI	A,':'
	STAX	D
	INX	D
NODRV:	MVI	B,11
FCOPY1: MOV	A,M
	CPI	SPACE
	JZ	NOCOPY
	STAX	D
	INX	D
NOCOPY: INX	H
	DCR	B
	RZ
	MOV	A,B
	CPI	3
	JNZ	FCOPY1
	MVI	A,'.'
	STAX	D
	INX	D
	JMP	FCOPY1

;	UTILITY SUBROUTINES:

;	DTOBIN - CONVERT A 5-DIGIT (MAX.) UNSIGNED DECIMAL NUMBER INTO A
;	BINARY VALUE.
;
;	ENTRY:	(C) = NO. OF DIGITS
;		(HL)  POINTS TO CHAR. STRING (MSD FIRST)
;
;	EXIT:	(DE) = CONVERTED NUMBER
;		'C' FLAG SET IF OVERFLOW (>64384) OR ERROR
;
;	USES:	ALL 8080 REGS.
;
DGTPTR: DS	2
DTOBIN: XRA	A
	MOV	D,A
	MOV	E,A		;CLEAR (DE) - SERVES AS ACCUMULATOR
	CMP	C		;MAKE SURE (C) NOT ZERO
	RZ
	PUSH	H
DTOB:	SHLD	DGTPTR		;SAVE (HL) AS DIGIT POINTER
	MOV	A,M		;GET DIGIT
	POP	H
	CALL	VALDGT		;CHECK FOR VALID DECIMAL DIGIT
	RC			;RETURN WITH ERROR IF NOT
	ANI	0FH		;CONVERT ASCII DIGIT TO BINARY
	XCHG			;SAVE SUM IN (HL)
	MOV	E,A		;MOVE DIGIT TO (DE)
	MVI	D,0
	MOV	B,C
	DCR	B		;PUT COUNT IN (B) = DIGIT COUNT -1
	JZ	DTOB$1		;IF =0, SKIP X10 ROUTINE
DEX10:	PUSH	H
	CALL	MU10		;MULTIPLY (DE) BY 10
	XCHG
	POP	H
	RC			;(B) TIMES - RETURN ON OVERFLOW
	DCR	B
	JNZ	DEX10
DTOB$1: DAD	D		;ADD PRODUCT TO RESULT
	RC			;RETURN ON OVERFLOW
	XCHG			;SAVE NEW SUM
	DCR	C
	RZ			;FINISHED IF COUNT=0
	PUSH	H
	LHLD	DGTPTR
	INX	H		;ELSE BUMP DIGIT POINTER
	JMP	DTOB		;& CONVERT NEXT DIGIT
MU10:	XCHG
	DAD	H
	RC
	MOV	D,H
	MOV	E,L
	DAD	H
	RC
	DAD	H
	RC
	DAD	D
	RET

;	SUBROUTINE - RETURNS WITH 'C' FLAG SET IF CHAR. IN (A) IS NOT
;	IN THE RANGE 0-9

VALDGT: CPI	'0'
	RC
	CPI	':'
	CMC
	RET

;	CHARACTER OUTPUT

COUT:	DS	0
	PUSH	H
	PUSH	D
	PUSH	B
	PUSH	PSW
	ANI	07FH
	MOV	E,A
	MVI	C,CONOUT
	CNZ	BDOS
	POP	PSW
	POP	B
	POP	D
	POP	H
	RET

;	WRITE CR + LF

CRLF:	MVI	A,CR
	CALL	COUT
	MVI	A,LF
	JMP	COUT

;	COMPRP = COMPARE REGISTER PAIR
;
;	ENTRY:	(HL), (DE) SET
;
;	EXIT:	'Z'=1 IF (HL)=(DE)
;		'C'=1 IF (HL)>(DE)
;
COMPRP: MOV	A,D
	CMP	H
	RNZ
	MOV	A,E
	CMP	L
	RET

;	SUBHLDE = SUBTRACT (DE) FROM (HL)
;
;	ENTRY:	(HL), (DE) SET
;
;	EXIT:	(HL) = (HL) - (DE)
;		'C' AND 'Z' FLAGS REFLECT RESULTS OF SUBTRACTION
;
;	USES:	F,H,L  ONLY
;
SUBHLDE: DS	 0
	PUSH	B
	MOV	B,A		;SAVE (A)
	XCHG
	CALL	COMPRP		;SET FLAGS
	XCHG
	MOV	A,B		;RESTORE (A)
	POP	B
	PUSH	PSW		;SAVE (A) AND FLAGS
	MOV	A,L
	SUB	E
	MOV	L,A		;DO SUBTRACTION
	MOV	A,H
	SBB	D
	MOV	H,A
	POP	PSW		;RESTORE (A) AND FLAGS
	RET

;	MEMORY DEFINITIONS:
;
FCB:	DS	FCBSIZ
OLDSTK: DS	2		;STACK POINTER ON ENTRY
ENDTXT: DS	2		;END OF TEXT IN BUFFER
FILSIZ: DS	2		;FILE SIZE IN RECORDS
NREAD:	DS	2		;NUMBER OF RECORDS TO READ FROM FILE
NUMEND: DS	2		;END OF NUMBER
	DS	40		;STACK AREA
STACK:	DS	0		;TOP OF STACK
NUMLIN: DW	20		;NUMBER OF LINES TO LIST
MSG0:	DB	CR,LF,'CP/M 2.X REQUIRED.',CR,LF,'$'
MSG1:	DB	'TAIL V. 1.1 BY JON DART'
	DB	CR,LF,CR,LF
	DB	'USAGE: TAIL -n filename',CR,LF
	DB	SPACE,SPACE,SPACE,SPACE,SPACE,SPACE,SPACE
	DB	'-n is optional, shows last n lines of file (default 20)','$'
MSG2:	DB	CR,LF,'CAN''T OPEN: '
FILNAM: DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,CR,LF,'$'

ENDDATA EQU	$
BUFFER	EQU	ENDDATA+10;
	END
