 ; io.asm
 ;  "Frame" program and I/O subroutines for general purpose assembly language
 ;  programs.  To use this package:
 ;  1. The first line in your file should be "include iodefs.txt".
 ;  2. Your main program must begin with the label "main" and end with rts.
 ;  3. Your .ld file should look like this, if your source file is called
 ;     myprog.asm:
 ;			!start
 ;			io
 ;			myprog
 ;			$
 ;  4. Pass arguments to subroutines in this package, and receive return
 ;     values, in D0 or A0 as specified.  All registers are preserved (but
 ;     subroutine rdchar does not preserve A0).
 ;  5. Always call subroutine makewindow at the beginning of your main
 ;     program, passing the address of your title string in A0.
 
 ; Include all necessary traps files
 
 include	m68klib.d
 include systraps.txt
 include quicktraps.txt
 include tooltraps.txt
 
 WINDWIDTH equ 492	; window dimensions
 WINDHEIGHT equ 280	
 X1	equ 8		; upper-left and lower-right coordinates
 Y1	equ 48		; for bounds rectangle for window
 X2	equ X1+WINDWIDTH
 Y2	equ Y1+WINDHEIGHT
 LMARG	equ 6		  ; text margins - left,
 RMARG	equ WINDWIDTH-10  ; right,
 TMARG	equ 15		  ; top,
 BMARG	equ WINDHEIGHT-15 ; bottom
 CR	equ 13		; ascii carriage return
 BS	equ 8		; ascii backspace
 DASH	equ 45		; ascii minus sign
 ZERO	equ 48		; ascii character for digit zero
 NINE	equ 57		; ascii character for digit nine
 PROGFONT equ 4		; Monaco font code
 ASCENT	equ 0		; offsets of fields in fontinfo record
 DESCENT	equ 2
 WIDMAX	equ 4
 LEADING	equ 6
 BUFSIZE equ 256		; size of input buffer
 
 xdef	start
 xref	main
 
 ;  Preserve registers in safe place; initialize graphics, font manager,
 ;  windows, cursor, and textedit; call main program; restore registers
 ;  and exit.
 
 start:	movem.l	D0-D7/A0-A6, -(SP)	; save all registers
 	lea	saveregs, A0
 	move.l	A6, (A0)
 	move.l	A7, 4(A0)
 	
 	move.l	A5, D0			; set up quickdraw
 	sub.l	#4, D0
 	move.l	D0, -(SP)
 	_INITGRAF
 	_INITFONT
 	_INITWINDOW
 	_INITCURSOR
 	_TEINIT
 	
 	bsr main			; call the main program
 	
 	lea	saveregs, A0
 	move.l	(A0), A6
 	move.l	4(A0), A7
 	movem.l	(SP)+, D0-D7/A0-A6
 	_EXITTOSHELL
 	
 
 ; Subroutine makewindow ( StringAddress )
 ;  Receives argument in A0
 ;	Creates and displays a desk-accessory style window with title
 ;	specified by string whose address is passed; flushes event queue
 ;	and initializes cursor position for text I/O
 
 xdef	makewindow
 
 makewindow:
 	movem.l	D0-D2/A0-A1, -(SP)	; save registers
 	clr.l	-(SP)			; space for return window pointer
 	clr.l	-(SP)			; nil wstorage pointer - window 
 					;  record storage will be allocated
 					;  on heap
 	pea	boundsrect		; determines window size and loc
 	move.l	A0, -(SP)		; window title
 	st	-(SP)			; visible flag true - draw window
 	move	#16, -(SP)		; procid - desk accessory type window
 	move.l	#-1, -(SP)		; behind pointer -1 means in front
 					;  of all other windows
 	sf	-(SP)			; goaway flag false - no goaway box
 	clr.l	-(SP)			; refcon (not used)
 	_NEWWINDOW
 	move.l	(SP), window(A5)
 	_SETPORT
 	clr.l	-(SP)
 	_NEWRGN				; create a region
 	lea	scrollrgnh, A0
 	move.l	(SP), (A0)
 	pea	scrollrect		; with same bounds as boundsrect
 	_RECTRGN			; (required later for scrolling)
 	move.w	#PROGFONT, -(SP)	; set font to Geneva (monofont)
 	_TEXTFONT
 	pea	fontinfo		; get information on font
 	_GETFONTINFO			;  dimension parameters
 	clr.w	D0			; accumulate line height in D0
 	add.w	fontinfo+ASCENT, D0
 	add.w	fontinfo+DESCENT, D0
 	add.w	fontinfo+LEADING, D0
 	lea	lnht, A0
 	move.w	D0, (A0)		; store in lnht
 	move.l	D0, -(SP)		; flush event queue
 	move.l	#$0000FFFF, D0
 	_FLUSHEVENTS
 	move.l	(SP)+, D0
 	move	#LMARG, -(SP)		; initialize cursor position
 	move	#TMARG, -(SP)
 	_MOVETO
 	movem.l	(SP)+, D0-D2/A0-A1	; restore registers
 	rts
 	
 ; Subroutine clearwindow
 ;	Erases contents of window and 'homes' pen to original position in
 ;	upper left corner.
 
 xdef	clearwindow
 
 clearwindow:
 	movem.l	D0-D2/A0-A1, -(SP)	; save registers
 	pea	scrollrect		; erase window contents
 	_ERASERECT
 	move.w	#LMARG, -(SP)		; home pen
 	move.w	#TMARG, -(SP)
 	_MOVETO
 	movem.l	(SP)+, D0-D2/A0-A1	; restore registers
 	rts
 	
 ; Subroutine rdchar 
 ;   Returns single character result in D0.
 ;	Waits for next keyboard character from event queue, echoes it on
 ;	screen and returns it in D0.  If character is CR or right margin has
 ;	been reached, moves cursor to left margin one line down.
 
 xdef	rdchar
 
 rdchar: lea	regbufe, A0
 	movem.l	D1-D2/A1, -(A0)		; save registers (but not A0)
 	move.w	#40, -(SP)		; mask for key-down or auto-key event
 	pea	evbuf			; pass address of event buffer
 	_GETNEXTEVENT
 	lea	evbuf, A0		; any characters?
 	tst.w	(A0)
 	beq	rdchar			; if not, keep waiting
 	clr.l	D0
 	move.b	5(A0), D0		; pass character to wrtchar
 	jsr	wrtchar
 	lea	regbuf, A0
 	movem.l	(A0)+, D1-D2/A1		; restore registers
 	rts
 	
 ; Subroutine wrtchar ( character )
 ;   Receives single character argument in D0
 ;	Draws character on screen; if character is CR or right margin has
 ;	been reached, moves pen to left margin one line down; if bottom
 ;	has been reached, scrolls up one line.
 
 xdef	wrtchar
 
 wrtchar:
 	movem.l	D1-D2/D5-D7/A0-A1, -(SP) ; save registers
 	move.l	D0, D5			; store character in D5
 	pea	penloc			; check pen location
 	_GETPEN
 	move.w	penloc+2, D7		; store x value of pen in D7
 	move.w	penloc, D6		; store y value of pen in D6
 	cmp.b	#BS, D5			; backspace typed?
 	bne	wrtchar1		; if not, skip code to erase char.
 	cmp.w	#LMARG, D7		; already at left margin?
 	beq	wrtchar4		; if so, skip code to erase char.
 	lea	fontinfo, A0		; compute and push inverse of
 	move.w	WIDMAX(A0), D0		; character width
 	neg.w	D0
 	move.w	D0, -(SP)
 	move.w	#0, -(SP)		; don't change pen's y coord
 	_MOVE				; move pen back one character pos.
 	move.w	D7, -(SP)		; push x coord for lower right corner
 	move.w	D6, D0			; add descent amount to y coord
 	add.w	fontinfo+DESCENT, D0	;  of lower right corner
 	move.w	D0, -(SP)		; of erase rect.
 	sub.w	fontinfo+ASCENT, D6	; compute coords for upper left
 	sub.w	fontinfo+WIDMAX, D7	; corner of erase rect
 	move.w	D7, -(SP)		; and push them
 	move.w	D6, -(SP)		
 	move.l	SP, A0			; push addr of erase rect
 	move.l	A0, -(SP)
 	_ERASERECT			; erase deleted character
 	add	#8, SP			; remove rectangle coords from stack
 	bra	wrtchar4		; skip ahead to return backspace
 wrtchar1:
 	cmp.b	#CR, D5			; carriage return?
 	beq	wrtchar2			; if so, skip to new-line generation
 	cmp.w	#RMARG, D7		; past right margin?
 	blt	wrtchar3			; if not, skip new-line generation
 wrtchar2: 				; generate new line
 	sub.w	#LMARG, D7		; compute delta x
 	neg.w	D7
 	move.w	D7, -(SP)
 	lea	lnht, A0
 	move.w	(A0), -(SP)
 	_MOVE				; position cursor for new line
 	cmp.w	#BMARG, D6		; past bottom margin?
 	blt	wrtchar3			; if not, skip past code for scroll
 	pea	scrollrect
 	move.w	#0, -(SP)
 	lea	lnht, A0
 	move.w	(A0), -(SP)
 	neg.w	(SP)
 	lea	scrollrgnh, A0
 	move.l	(A0), -(SP)		; else, scroll up by one
 	_SCROLLRECT			; line height
 	move.w	#0, -(SP)
 	lea	lnht, A0
 	move.w	(A0), -(SP)		; move pen up accordingly
 	neg.w	(SP)
 	_MOVE
 wrtchar3:
 	move.w	D5, -(SP)
 	_DRAWCHAR			; display character
 wrtchar4:
 	move.l	D5, D0			; return character in D0
 	movem.l (SP)+, D1-D2/D5-D7/A0-A1 ; restore registers
 	rts
 	
 ; Subroutine wrtdec ( num: integer )
 ;   Receives integer argument in D0.
 ;	If argument is negative, negates it and outputs a minus sign; calls
 ;	subroutine prtdec to output digits of number
 
 xdef	wrtdec
 
 wrtdec:	movem.l D0-D2/A0-A1, -(SP)	; save registers
 	tst.w	D0			; negative?
 	bge	wrtdec1			
 	neg.w	D0			; if so, negate num
 	move.w	D0, -(SP)		; save num
 	;clr.w	D0
 	move.b	#DASH, D0		; output minus sign
 	jsr	wrtchar
 	move.w	(SP)+, D0		; restore num to D0
 wrtdec1:
 	swap	D0
 	clr.w	D0
 	swap	D0
 	bsr	prtdec			; output num
 	movem.l (SP)+, D0-D2/A0-A1	; restore registers
 	rts
 	
 ; Subroutine prtdec (num: integer)
 ;   Receives integer argument in D0.
 ;	Recursively outputs digits of integer in decimal to terminal.
 
 prtdec:	divs	#10, D0			; num <- num DIV 10
 	tst.w	D0			; result equals zero?
 	beq	prtdec1
 	move.l	D0, -(SP)		; if not, call prtdec recursively
 	swap	D0
 	clr.w	D0			; (first getting rid of remainder)
 	swap	D0
 	bsr	prtdec			; with num DIV 10
 	move.l	(SP)+, D0
 prtdec1:
 	swap	D0			; num MOD 10
 	add.b	#ZERO, D0		; compute ascii code for digit
 	jsr	wrtchar			; output digit
 	rts
 	
 ; Subroutine rddec 
 ;   Returns integer result in D0.
 ;	Inputs signed word integer value (decimal), ignores all non-numeric
 ;	characters except for minus sign, returns result on encountering
 ;	CR.
 
 xdef	rddec
 
 rddec:	movem.l D1-D5/A0-A1, -(SP)	; save registers
 	lea	inbuf, A0
 	jsr	rdstrz			; input line to buffer
 	clr.w	D0			; sum = 0
 	clr.w	D3			; character holder = 0
 	clr.b	D4			; digits read = false
 	move.w	#1, D5			; sign = +
 rddec1: move.b	(A0)+, D3		; end of input line?
 	beq 	rddec3			; if so, return result
 	cmp.b	#DASH, D3		; minus sign?
 	bne	rddec2
 	tst.b	D4			; if so, any digits read yet?
 	bne	rddec1			; if so, ignore minus sign
 	neg.w	D5			; else, sign = -
 	bra	rddec1
 rddec2: cmp.b	#ZERO, D3		; less than '0'?
 	blt	rddec1			; if so, ignore
 	cmp.b	#NINE, D3		; greater than '9'?
 	bgt	rddec1			; if so, ignore
 	move.b	#1, D4			; digits read = true
 	muls	#10, D0			; sum = sum * 10
 	sub.w	#ZERO, D3		; subtract ascii code for 0 from char
 					;  to get digit value
 	add.w	D3, D0			; sum = sum + digit
 	bra	rddec1			; get next character
 rddec3:	muls	D5, D0			; sum = sum * sign
 	movem.l (SP)+, D1-D5/A0-A1	; restore registers
 	rts
 	
 ; Subroutine rdstrz ( BufferAddress )
 ;   Receives address of destination buffer in A0.
 ;	Inputs characters to buffer specified by destination address
 ;	until CR is read; terminates string with zero byte; if backspace
 ;	encountered, deletes last character in buffer, backspace not stored.
 ;	Inputs maximum of 255 characters to buffer (plus zero byte), flushes
 ;	excessive input until next CR encountered (for buffers less than
 ;	256 bytes long, buffer overflow is a possibility).
 
 xdef	rdstrz
 
 rdstrz:	movem.l D0-D3/A0-A2, -(SP)	; save registers
 	clr.w	D3			; initialize counter
 	movea.l	A0, A2			; save addr of buffer in A2
 rdstrz1:
 	jsr	rdchar			; input next character
 	cmp.b	#CR, D0			; CR?
 	beq	rdstrz5			; if so, terminate string
 	cmp.b	#BS, D0			; backspace?
 	bne	rdstrz2	
 	tst.w	D3			; if so, is buffer empty?
 	beq	rdstrz1			; if so, get next character
 	clr.b	-(A2)			; else, delete last character
 	sub.w	#1, D3			; decrement counter
 	bra	rdstrz1			; input another character
 rdstrz2:
 	cmp.w	#BUFSIZE-1, D3		; buffer full?
 	bne	rdstrz4
 rdstrz3:
 	jsr	rdchar			; if so,
 	cmp.b	#CR, D0			; discard input characters till
 	bne	rdstrz3			; CR found
 	bra	rdstrz5			; and terminate string
 rdstrz4:
 	add.w	#1, D3			; else, increment character count
 	move.b	D0, (A2)+		; and move input character to
 					;  next buffer position
 	bra	rdstrz1			; input another character
 rdstrz5:
 	clr.b	(A2)			; zero terminate
 	movem.l (SP)+, D0-D3/A0-A2	; restore registers
 	rts
 	
 ; Subroutine wrtstrz ( BufferAddress )
 ;   Receives address of zero-terminated string in A0.
 ;	Outputs characters of string on screen until zero byte is reached.
 
 xdef	wrtstrz
 
 wrtstrz:
 	movem.l D0/A0, -(SP)		; save registers
 wrtstrz1:
 	move.b	(A0)+, D0		; pass char to wrtchar
 	beq	wrtstrz2		; return when zero byte reached
 	jsr	wrtchar			; output character
 	bra	wrtstrz1		; get next byte
 wrtstrz2:
 	movem.l (SP)+, D0/A0		; restore registers
 	rts
 	
 ; Subroutine rdstrc ( BufferAddress )
 ;   Receives address of destination buffer in A0.
 ;	Inputs characters to buffer specified by destination address
 ;	until CR is read; places count byte at start of string; if backspace
 ;	encountered, deletes last character in buffer, backspace not stored.
 ;	Inputs maximum of 255 characters to buffer (plus count byte), flushes
 ;	excessive input until next CR encountered (for buffers less than
 ;	256 bytes long, buffer overflow is a possibility).
 
 xdef	rdstrc
 
 rdstrc:	movem.l D0-D3/A0-A3, -(SP)	; save registers
 	movea.l	A0, A2			; save addr of buffer in A2
 	movea.l	A0, A3			; and A3
 	tst.b	(A2)+			; increment pointer past count byte
 	clr.b	D3			; initialize counter
 rdstrc1:
 	jsr	rdchar			; input next character
 	cmp.b	#CR, D0			; CR?
 	beq	rdstrc5			; if so, terminate string
 	cmp.b	#BS, D0			; backspace?
 	bne	rdstrc2	
 	tst.b	D3			; if so, is buffer empty?
 	beq	rdstrc1			; if so, get next character
 	clr.b	-(A2)			; else, delete last character
 	sub.b	#1, D3			; decrement counter
 	bra	rdstrc1			; input another character
 rdstrc2:
 	cmp.b	#BUFSIZE-1, D3		; buffer full?
 	bne	rdstrc4
 rdstrc3:
 	jsr	rdchar			; if so,
 	cmp.b	#CR, D0			; discard input characters till
 	bne	rdstrc3			; CR found
 	bra	rdstrc5			; and terminate string
 rdstrc4:
 	add.b	#1, D3			; else, increment character count
 	move.b	D0, (A2)+		; and move input character to
 					;  next buffer position
 	bra	rdstrc1			; input another character
 rdstrc5:
 	move.b	D3, (A3)		; insert count byte
 	movem.l (SP)+, D0-D3/A0-A3	; restore registers
 	rts
 	
 ; Subroutine wrtstrc ( BufferAddress )
 ;   Receives address of count string in A0.
 ;	Outputs number of characters specified by count-byte.
 
 xdef	wrtstrc
 
 wrtstrc:
 	movem.l D0/D3/A0, -(SP)		; save registers
 	move.b	(A0)+, D3		; store count in D3
 wrtstrc1:
 	tst.b	D3			; all characters output?
 	beq	wrtstrc2		; then quit
 	move.b	(A0)+, D0		; pass char to wrtchar
 	jsr	wrtchar			; output character
 	sub.b	#1, D3			; decrement counter
 	bra	wrtstrc1		; get next byte
 wrtstrc2:
 	movem.l (SP)+, D0/D3/A0		; restore registers
 	rts
 	
 
 ; data for shell program
 
 saveregs: dcb.l	2, 0	; space for storing saved registers
 evbuf:	dcb.l	4, 0	; event buffer
 
 ; data for subroutine makewindow
 
 boundsrect:	dc.w	Y1, X1, Y2, X2		; the window rectangle in
 						;  global coordinates
 scrollrect:	dc.w	0, 0, WINDHEIGHT, WINDWIDTH ; the window rectangle
 						;  in local coordinates
 scrollrgnh:	dc.l	0			; handle for scroll region
 fontinfo:	dcb.w	4, 0			; buffer for record returned
 						;  by _GETFONTINFO
 lnht:		dc.w	0			; buffer for height of line
 						;  of text
 ; data for subroutine rdchar
 
 penloc:	dc.l	0		; storage for pen location coordinates
 regbuf:	dcb.l	3, 0		; storage for saved registers
 regbufe:
 
 ; data for subroutine rddec
 
 inbuf:	dcb.b	BUFSIZE, 0	; input buffer
 
 ; global storage
 
 window:	ds.l	1
 
 	end

