; Format input lines to standard output

	INCLUDE	"/include/init.i"

;	OPT	D+

; Local Equs

FormatBufSize	EQU	1500
MaxLineSize	EQU	512
MaxFormatArgs	EQU	20

; Variable storage

	STRUCT	ArgArray,(1+1)*4
	STRUCT	ArgStream,MaxFormatArgs*4
	STRUCT	FormatClipInfo,2*4
	LONG	StdIn
	LONG	StdOut
	LONG	FormatBufPtr
	SIZE

; Regs

ByteFetch	EQUR	D4
ReadBytesLeft	EQUR	D5
LineBufPtr	EQUR	D6
LineBufLeft	EQUR	D7
LineEndPtr	EQUR	A2
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

; Open the standard input & output

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

; Alloc tracked mem for the string format buffer.

	LEA	OutOfMem(PC),ErrorStrPtr
	MOVE.L	#FormatBufSize,D0
	MOVEQ	#0,D1
	CALL	ArpAllocMem
	MOVE.L	D0,FormatBufPtr(GP)
	BEQ	ErrorExit

; Allocate the read-line buffer

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

; Initialize the argument stream

	MOVEQ	#MaxFormatArgs-1,D0
	LEA	ArgStream(GP),A0
InitArgStreamLongs:
	MOVE.L	LineBufPtr,(A0)+
	DBRA	D0,InitArgStreamLongs


; ** This is an intelligent ReadLine routine.

	MOVEQ	#0,ReadBytesLeft

; 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,A0
	ADDQ.L	#1,ByteFetch
	SUBQ.L	#1,ReadBytesLeft
	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
	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.S	ErrorExit
	BNE.S	NotYetDone
	CMP.L	LineBufPtr,LineEndPtr
	BEQ.S	Exit
NotYetDone:
	MOVE.L	D2,ByteFetch
	MOVE.L	D0,ReadBytesLeft
	BNE	CopyLineBytes

; Null terminate the line.

GotLine:
	CLR.B	(LineEndPtr)

; Format things, terminate with a LF, and write the format buffer

	BSR.S	VanillaDoFmt
	MOVE.L	FormatBufPtr(GP),A0
	MOVE.B	#10,-1(A0,D0.L)

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

; Check if user hit ^C 

	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 sub uses exec's RawDoFmt to do string formatting while clipping
; any surplus data to fit the buffer size. Arps format routines have
; no overflow checks, and since the user is not guaranteed to act
; sanely, clipping is essential.
; I : Assumes a valid format string and buffer
; O : The format buffer contain the formatted string.
;   : D0.L = Size of formatted string.

Vanilla_Regs	REG	A2/A3/A6

VanillaDoFmt:
	MOVEM.L	Vanilla_Regs,-(SP)
	MOVE.L	ArgArray(GP),A0
	LEA	ArgStream(GP),A1
	LEA	DoClippedBufferFill(PC),A2
	LEA	FormatClipInfo(GP),A3
	MOVE.L	FormatBufPtr(GP),(A3)
	MOVE.L	(A3),4(A3)
	ADD.L	#FormatBufSize,4(A3)
	MOVE.L	$4.W,A6				; exec
	CALL	RawDoFmt
	MOVE.L	(A3),D0
	SUB.L	FormatBufPtr(GP),D0
	MOVEM.L	(SP)+,Vanilla_Regs
	RTS

; Assumes A0 is usable, not clear from autodocs whether this is true.
; (Default calling conventions and ROM code suggest it is)

DoClippedBufferFill:
	MOVE.L	(A3)+,A0
	CMP.L	(A3),A0
	BNE.S	NotYetAtBufEnd
	SUBQ.L	#1,A0
NotYetAtBufEnd:
	MOVE.B	D0,(A0)+
	MOVE.L	A0,-(A3)
	RTS

; The string section

Template:
	DC.B	'FormatString/a',0
HelpMsg:
	DC.B	'PForm - Pipe formatted lines.',10
	DC.B	'Usage: PForm "prefix %[width.limit]s postfix"',0
OutOfMem:
	DC.B	'Out of memory!',0
ReadError:
	DC.B	'Error while reading',0
WriteError:
	DC.B	'Error while writing',0
	CNOP	0,2

	END
