; Filter non-text bytes from pipe stream.

	INCLUDE	"/include/init.i"

; Local Equs
; FilTex uses a buffer split into two 1000 byte parts, the first for writing,
; the last part for reading. Reads/Writes are HalfBufSize bytes large max.

DefaultThreshold EQU	4
BufSize		 EQU	2000			; Keep this one < 32K
HalfBufSize	 EQU	BufSize/2

; Variable storage

	STRUCT	ArgArray,(4+1)*4
	LONG	InputHandle
	LONG	StdOutput
	WORD	Threshold
	SIZE

; Regs

WriteBufIndex	EQUR	D4
ReadBufIndex	EQUR	D5
ReadBytesLeft	EQUR	D6
LatestByte	EQUR	D7
BufferPtr	EQUR	A2
FilterTablePtr	EQUR	A3
ErrorStrPtr	EQUR	A4

; Start

	STACK	4000
	INIT

; Let ARP interpret the commandline

	MOVE.L	ComLineBase(GP),A0
	MOVE.L	ComLineSize(GP),D0
	LEA	HelpMsg(PC),A1
	LEA	ArgArray(GP),A2
	LEA	Template(PC),A3
	MOVE.L	A1,(A2)
	CALL	GADS
	MOVE.L	(A2),ErrorStrPtr
	TST.L	D0
	BEQ	ErrorExit
	BMI	ErrorExit

; Fetch & check the threshold value.

	LEA	BadThreshold(PC),ErrorStrPtr
	MOVE.W	#DefaultThreshold,Threshold(GP)
	MOVE.L	ArgArray+4(GP),A0
	MOVE.L	A0,D0
	BEQ.S	UseDefault
	CALL	Atol
	BEQ	ErrorExit
	CMP.L	#HalfBufSize,D0
	BHI	ErrorExit
	MOVE.W	D0,Threshold(GP)
	BEQ	ErrorExit
UseDefault:

; Check if the source is STDIN, if so open that.

	MOVE.L	ArgArray(GP),A0
	LEA	StdinID(PC),A1
	CALL	Strcmp
	BNE.S	NoStdin
	CALL	Input
	MOVE.L	D0,InputHandle(GP)
	BRA.S	InFileOpened
NoStdin:

; Else open a normal source file (Tracked)

	LEA	SourceOpenError(PC),ErrorStrPtr
	MOVE.L	ArgArray(GP),D1
	MOVE.L	#MODE_OLDFILE,D2
	CALL	ArpOpen
	MOVE.L	D0,InputHandle(GP)
	BEQ	ErrorExit
InFileOpened:

; Fetch the StdOutput

	CALL	Output
	MOVE.L	D0,StdOutput(GP)

; Alloc tracked mem for the read/write buffer.

	LEA	OutOfMem(PC),ErrorStrPtr
	MOVE.L	#BufSize+1,D0			; add 1 to allow for LF termination
	MOVEQ	#0,D1
	CALL	ArpAllocMem
	TST.L	D0
	BEQ	ErrorExit
	MOVE.L	D0,BufferPtr

; Initialize for the filtered copy loop

	LEA	FilterTableBase(PC),FilterTablePtr
	MOVEQ	#0,WriteBufIndex

; Read next chunk of bytes.

DoNextRead:
	LEA	ReadError(PC),ErrorStrPtr
	MOVEQ	#0,ReadBufIndex
	MOVE.W	#HalfBufSize,ReadBufIndex
	MOVE.L	InputHandle(GP),D1
	MOVE.L	BufferPtr,D2
	ADD.L	ReadBufIndex,D2
	MOVE.L	ReadBufIndex,D3
	CALL	Read
	MOVE.W	D0,ReadBytesLeft
	BMI	ErrorExit
	BEQ.S	TryToWriteWriteBuffer

; Find text bytes.

CheckForTextBytes:
	MOVEQ	#0,LatestByte
	MOVE.B	0(BufferPtr,ReadBufIndex.W),LatestByte
	ADDQ.W	#1,ReadBufIndex
	SUBQ.W	#1,ReadBytesLeft
	TST.B	0(FilterTablePtr,LatestByte.W)
	BEQ.S	TryToWriteWriteBuffer

; Process text bytes

	MOVE.B	LatestByte,0(BufferPtr,WriteBufIndex.W)
	ADDQ.W	#1,WriteBufIndex
	CMP.W	#HalfBufSize,WriteBufIndex
	BNE.S	CheckReadBufStatus

; Check if the number of bytes to write equals or exceeds the threshold

TryToWriteWriteBuffer:
	CMP.W	Threshold(GP),WriteBufIndex
	BLO.S	NeLeWritezPas

; Check if string is null-terminated (if requested to do so)

	TST.L	ArgArray+12(GP)			; Check NULT switch
	BEQ.S	NoNullCheck
	TST.B	LatestByte
	BNE.S	NeLeWritezPas
NoNullCheck:

; Terminate write with a linefeed if needed & not prohibited

	CMP.B	#10,-1(BufferPtr,WriteBufIndex.W)
	BEQ.S	DoTheWrite
	TST.L	ArgArray+8(GP)			; Check NOLF switch
	BNE.S	DoTheWrite
	MOVE.B	#10,0(BufferPtr,WriteBufIndex.W)
	ADDQ.W	#1,WriteBufIndex

; The actual write to the StdOutput

DoTheWrite:
	LEA	WriteError(PC),ErrorStrPtr
	MOVE.L	StdOutput(GP),D1
	MOVE.L	BufferPtr,D2
	MOVE.W	WriteBufIndex,D3
	EXT.L	D3
	CALL	Write
	CMP.L	D0,D3
	BNE.S	ErrorExit

; Check if user hit ^C 

	SUB.L	A1,A1
	CALL	CheckAbort
	MOVE.L	A1,ErrorStrPtr
	TST.L	D0
	BNE.S	ErrorExit
NeLeWritezPas:

; Reset the write buffer index.

	MOVEQ	#0,WriteBufIndex

; Check if no more read bytes left, if so read, exit if last read had zero length
; (An adjective, verb and a noun. YOU try 'n make a natural language interpreter!)

CheckReadBufStatus:
	TST.W	ReadBytesLeft
	BNE	CheckForTextBytes
	CMP.W	#HalfBufSize,ReadBufIndex
	BNE	DoNextRead

; Done, cleanup

Exit:
	CLR.W	ReturnCode(GP)
	SUB.L	ErrorStrPtr,ErrorStrPtr
ErrorExit:

; Display error string, if any, and exit

	MOVE.L	ErrorStrPtr,D0
	BEQ.S	NoErrorMsg
	MOVE.L	D0,A1
	CALL	Puts
NoErrorMsg:
	RTS

; The string section

Template:
	DC.B	'File/a,THRESH/k,NOLF/s,NULT/s',0
HelpMsg:
	DC.B	'FilTex - filter out non-text.',10
	DC.B	'Usage: FilTex <File | STDIN> [THRESH #] [NOLF] [NULT]',0
StdinID:
	DC.B	'STDIN',0
BadThreshold:
	DC.B	'Illegal threshold value',0
SourceOpenError:
	DC.B	'Could not open source file',0
OutOfMem:
	DC.B	'Out of memory!',0
ReadError:
	DC.B	'Error while reading',0
WriteError:
	DC.B	'Error while writing',0
	CNOP	0,4

; This is the filtering array

	DC.B	'TextTab:'
FilterTableBase:
;		 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
	DC.B	000,000,000,000,000,000,000,000,000,$09,$0A,000,000,000,000,000 ;0
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;1
	DC.B	' ','!','"','#','$','%','&',$27,'(',')','*','+',',','-','.','/' ;2
	DC.B	'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?' ;3
	DC.B	000,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O' ;4
	DC.B	'P','Q','R','S','T','U','V','W','X','Y','Z','[','\',']','^','_' ;5
	DC.B	000,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o' ;6
	DC.B	'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',000 ;7
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;8
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;9
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;A
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;B
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;C
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;D
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;E
	DC.B	000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ;F
;		 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
	END
