; Clip input lines on specified locations and write result to standard output.

       INCLUDE "/include/init.i"

;      OPT     D+

; Local Equs

MaxLineSize    EQU     512

; Variable storage

       STRUCT  ArgArray,(4+1)*4
       LONG    StdIn
       LONG    StdOut
       LONG    ByteFetch
       LONG    ReadBytesLeft
       LONG    Count1
       LONG    Count2
       LONG    Delta1
       LONG    Delta2
       LONG    ID1StrPtr
       LONG    ID2StrPtr
       LONG    ID1Exclusive
       LONG    ID2Exclusive
       SIZE

; Regs


Delta          EQUR    D4
Count          EQUR    D5
LineBufPtr     EQUR    D6
LineBufLeft    EQUR    D7
LineEndPtr     EQUR    A2
IDStrPtr       EQUR    A3
ErrorStrPtr    EQUR    A4

; Startup

       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

; Interpret the count values

       MOVE.L  ArgArray+0*4(GP),A0
       BSR     GetSignedNum
       BEQ     ErrorExit
       MOVE.L  D0,Count1(GP)
       MOVE.L  D1,Delta1(GP)

       MOVE.L  ArgArray+2*4(GP),A0
       BSR     GetSignedNum
       BEQ     ErrorExit
       MOVE.L  D0,Count2(GP)
       MOVE.L  D1,Delta2(GP)

; Interpret the ID strings

       LEA     EmptyString(PC),A1

       MOVE.L  ArgArray+1*4(GP),D0
       BNE.S   ID1StringSpecified
       MOVE.L  A1,D0
ID1StringSpecified:
       MOVE.L  D0,A0

       CMP.B   #'x',(A0)
       BNE.S   NoID1Exclusion
       ADDQ.L  #1,A0
       ADDQ.L  #1,ID1Exclusive(GP)
NoID1Exclusion:
       MOVE.L  A0,ID1StrPtr(GP)

       MOVE.L  ArgArray+3*4(GP),D0
       BNE.S   ID2StringSpecified
       MOVE.L  A1,D0
ID2StringSpecified:
       MOVE.L  D0,A0

       CMP.B   #'x',(A0)
       BNE.S   NoID2Exclusion
       ADDQ.L  #1,A0
       ADDQ.L  #1,ID2Exclusive(GP)
NoID2Exclusion:
       MOVE.L  A0,ID2StrPtr(GP)

; Open the standard input & output

       CALL    Input
       MOVE.L  D0,StdIn(GP)
       CALL    Output
       MOVE.L  D0,StdOut(GP)

; Allocate the read-line buffer

       LEA     OutOfMem(PC),ErrorStrPtr
       MOVE.L  #MaxLineSize+4+MaxLineSize,D0   ; 2 part buf with 0 term space.
       MOVEQ   #0,D1
       CALL    ArpAllocMem
       MOVE.L  D0,LineBufPtr
       BEQ     ErrorExit


; ** This is an intelligent ReadLine routine.

; Reinitialize the line buffer. (First part of dual buffer space)

DoForEachLine:
       LEA     ReadError(PC),ErrorStrPtr
       MOVE.L  LineBufPtr,LineEndPtr
       MOVE.L  #MaxLineSize,LineBufLeft

; Extract lines from the readbuffer into the line buffer.

       BRA.S   ExtractEntry
CopyLineBytes:
       MOVE.L  ByteFetch(GP),A0
       ADDQ.L  #1,ByteFetch(GP)
       SUBQ.L  #1,ReadBytesLeft(GP)
       MOVE.B  (A0),D0
       CMP.B   #10,D0
       BEQ.S   GotLine
       MOVE.B  D0,(LineEndPtr)+
       SUBQ.L  #1,LineBufLeft
       BEQ.S   GotLine
ExtractEntry:
       TST.L   ReadBytesLeft(GP)               ; Stack vars 0 init
       BNE     CopyLineBytes

; Refill the read buffer.

       MOVE.L  StdIn(GP),D1
       MOVE.L  LineBufPtr,D2
       MOVE.L  #MaxLineSize,D3
       ADD.L   D3,D2
       ADDQ.L  #4,D2
       CALL    Read
       TST.L   D0
       BMI     ErrorExit
       BNE.S   NotYetDone
       CMP.L   LineBufPtr,LineEndPtr
       BEQ.S   Exit
NotYetDone:
       MOVE.L  D2,ByteFetch(GP)
       MOVE.L  D0,ReadBytesLeft(GP)
       BNE     CopyLineBytes

; Adjust end of line pointer and weed out empty lines

GotLine:
       SUBQ.L  #1,LineEndPtr
       CMP.L   LineBufPtr,LineEndPtr
       BLO     DoForEachLine

; Find the initial clip postition

       MOVE.L  LineBufPtr,A0
       MOVE.L  Delta1(GP),Delta
       BPL.S   InitialPosSet
       MOVE.L  LineEndPtr,A0
InitialPosSet:
       MOVE.L  Count1(GP),Count
       MOVE.L  ID1StrPtr(GP),IDStrPtr  
       BSR.S   FindClipPosition
       BNE     DoForEachLine
       MOVE.L  A0,D3

; Find the secondary clip postition.

       MOVE.L  Delta2(GP),Delta
       MOVE.L  Count2(GP),Count
       MOVE.L  ID2StrPtr(GP),IDStrPtr
       BSR.S   FindClipPosition
       BNE     DoForEachLine

; Adjust for direction, adjust for exclusion and compute the length.

       MOVE.L  ID1Exclusive(GP),D0
       MOVE.L  ID2Exclusive(GP),D1
       CMP.L   A0,D3
       BHI.S   InvertedDirection
       EXG     D0,D1
       EXG     A0,D3
InvertedDirection:
       ADD.L   D1,A0
       SUB.L   D0,D3
       SUB.L   A0,D3
       BMI     DoForEachLine
       ADDQ.L  #2,D3

; Terminate with a LF and write the line clip.

       LEA     WriteError(PC),ErrorStrPtr
       MOVE.B  #10,-1(A0,D3.L)
       MOVE.L  StdOut(GP),D1
       MOVE.L  A0,D2
       CALL    Write
       CMP.L   D0,D3
       BNE.S   ErrorExit
WriteDone:

; Check if user hit ^C 

SkipWrite:
       SUB.L   A1,A1
       CALL    CheckAbort
       MOVE.L  A1,ErrorStrPtr
       TST.L   D0
       BNE.S   ErrorExit
       BRA     DoForEachLine

; 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

; Subroutines

; This subroutine tried to find the CNTth position in the current line (relative
; to the initial position) which matches the ID string.
; I : A0 = Initial search position
; O : A0 = Clip position
;   : Z=1 if search succeeded.
;   : LineBufPtr,LineEndPtr,Delta,Count,IDStrPtr should be initialized

FindClipPosition:

; Set the boundary to check with in D0 (Given position movement direction)

       MOVE.L  LineBufPtr,D0
       TST.L   Delta
       BMI.S   FetchedRelevantBoundary
       MOVE.L  LineEndPtr,D0
FetchedRelevantBoundary:

; Check if all matches found or boundary reached. If not move current position.

       TST.L   Count
CheckNextByte:
       BEQ.S   GotClipPosition
NoByteMatch:
       CMP.L   D0,A0
       BEQ.S   AtLineBoundary
       ADD.L   Delta,A0

; Check byte on the current position matched the ID string.

       TST.B   (IDStrPtr)
       BEQ.S   ByteMatched
       MOVE.L  IDStrPtr,A1
       MOVE.B  (A0),D1
TryToMatchNextByte:
       CMP.B   (A1)+,D1
       BEQ.S   ByteMatched
       TST.B   (A1)
       BEQ     NoByteMatch
       BRA     TryToMatchNextByte
ByteMatched:
       SUBQ.L  #1,Count
       BRA     CheckNextByte

; Done. Take care of the Z-flag.

AtLineBoundary:
       TST.B   (IDStrPtr)
GotClipPosition:
       RTS

; This sub decodes an ASCII buffer containing a signed number to a positive
; number and a sign boolean. It also sets ErrorStrPtr to "Invalid count"
; I : A0 = Ptr ASCII buffer
; O : D0.L = Integer >= 0
;   : D1.L = +1 if positive, -1 if negative
;   : Z = 1 if error

GetSignedNum:
       LEA     InvalidCnt(PC),ErrorStrPtr
       MOVEQ   #-1,D1
       CMP.B   #'-',(A0)+
       BEQ.S   ConvertToLong
       MOVEQ   #+1,D1
       CMP.B   #'+',-(A0)
       BNE.S   ConvertToLong
       ADDQ.L  #1,A0
ConvertToLong:
       MOVE.L  D1,-(SP)
       CALL    Atol
       MOVEM.L (SP)+,D1
       RTS

; The string section

Template:
       DC.B    'CNT1/a,ID1/k,CNT2/a,ID2/k',0
HelpMsg:
       DC.B    'Clip - Pipe through clipped lines.',10
       DC.B    'Usage: Clip <Cnt1> [ID1 [x]"charlist"] <RelCnt2> [ID2 [x]"charlist"]',0
OutOfMem:
       DC.B    'Out of memory!',0
InvalidCnt:
       DC.B    'Invalid count',0
ReadError:
       DC.B    'Error while reading',0
WriteError:
       DC.B    'Error while writing',0
EmptyString:
       DC.B    0
       CNOP    0,2

       END
