; 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
