; 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
