;++
;
; Select specific pages of a text file for printing (or whatever).
;
; By John Wilson.
;
; 11/16/95	JMBW	Created.
;
;--
	.radix	8
	locals
;
bufsiz=	32768d/10h	;I/O buffer size in paragraphs
;
nrange=	20d		;# pages or ranges of pages specifiable with /PAGES
;
bs=	10
tab=	11
lf=	12
ff=	14
cr=	15
;
kw	macro	text,addr
kh=	0
ki=	0
	irpc	kc,&text
ki=	ki+1
ifidn <&kc>,<->
kh=	ki
endif
	endm ;; irpc
ife kh
	.err	No hyphen in string:  &text
	exitm
endif ;; ife kh
	db	kh-1,ki-1	;;len to match, total len
	irpc	kc,&text	;;keyword text
ifdif <&kc>,<->
	db	'&kc'
endif
	endm ;; irpc
	dw	&addr
	endm
;
callr	macro	dest		;;call and return
	jmp	&dest
	endm
;
code	segment
	assume	cs:code
	org	100h
start:	cld			;always move forwards
	; make sure we have enough memory to run
	cmp	sp,offset pdl	;enough?
	jb	nomem		;no
	mov	sp,offset pdl	;shrink stack
	mov	bx,((pdl-start)/10h)+10h ;# paragraphs to keep
	mov	ah,4Ah		;func=setblock
	int	21h
	; get character for parsing switches
	mov	ax,3700h	;func=get SWITCHAR
	int	21h		;into dl
	mov	ds:swchar,dl	;save
	; scan command line
	mov	si,80h		;point at command line
	lodsb			;get length
	cbw			;ah=0
	mov	cx,ax		;copy
@@1:	; get next word or switch
	call	skip		;skip blanks, tabs
	jc	go		;that's all
	cmp	al,ds:swchar	;switch?
	je	@@3		;yes
	; it's a filename
	call	getw		;get it
	cmp	byte ptr ds:inmlen,0 ;do we have an input name already?
	jnz	@@2		;yes
	mov	ds:inmlen,dl	;this is it, save
	mov	ds:iname,bx
	jmp	short @@1
@@2:	cmp	byte ptr ds:onmlen,0 ;how about an output name?
	jnz	@@4		;yes, so what the hell is this?
	mov	ds:onmlen,dl	;save output filename
	mov	ds:oname,bx
	jmp	short @@1
@@3:	; switch
	inc	si		;skip the '/'
	dec	cx
	call	getw		;get the switch name
	jc	@@4		;none
	mov	ax,offset swtchs ;point at switch table
	call	tbluk		;look up
	jc	@@4
	call	ax		;process it
	jmp	short @@1
@@4:	; syntax error, print usage blurb
	mov	di,offset usage	;pt at string
	mov	cx,lusage	;length
	push	cx		;save
	push	di
	mov	al,'~'		;char to replace with SWITCHAR
	mov	ah,ds:swchar	;SWITCHAR
@@5:	repne	scasb		;find it?
	jne	@@6		;no
	mov	byte ptr [di-1],ah ;replace
	jmp	short @@5	;look more
@@6:	pop	dx		;restore ptr, len
	pop	cx
	jmp	short errmsg
nomem:	; insufficient memory
	mov	dx,offset nomem1 ;pt at msg
	mov	cx,lnmem1
errmsg:	; print err msg, dx=ptr, cx=length
	push	cs		;guarantee ds=cs
	pop	ds
	mov	bx,0002h	;handle=STDERR
	mov	ah,40h		;func=write
	int	21h
	mov	ax,4C01h	;func=punt
	int	21h
synerr:	mov	dx,offset syerr1 ;pt at msg
	mov	cx,lsyer1
	jmp	short errmsg
;+
;
; Begin processing.
;
; Open files, allocate buffers, find our first page.
;
;-
go:	; open input file
	xor	ch,ch		;ch=0
	mov	cl,ds:inmlen	;get length of input filename
	jcxz	@@1		;none (and no output file), leave STDIN/OUT
	mov	si,ds:iname	;get input filename
	mov	di,offset fnbuf	;pt at buf (es still =ds from entry)
	mov	dx,di		;save ptr
	rep	movsb		;copy
	mov	[di],cl		;mark end
	mov	ax,3D00h	;func=open /RONLY
	int	21h
	jc	@@5
	mov	ds:inhnd,ax	;save handle
	mov	ds:outhnd,0004h	;input file exists, default output=PRN
	mov	cl,ds:onmlen	;get length of output filename
	jcxz	@@1		;none, leave STDPRN
	mov	si,ds:oname	;get output filename
	mov	di,offset fnbuf	;pt at buf
	mov	dx,di		;save ptr
	rep	movsb		;copy
	mov	[di],cl		;mark end
	mov	ah,3Ch		;func=create (cx=0 from REP MOVSB)
	int	21h
	jc	@@6
	mov	ds:outhnd,ax	;save handle
@@1:	; allocate I/O buffers
	mov	bx,bufsiz	;ask for input buf
@@2:	mov	ah,48h		;func=getblock
	int	21h
	jnc	@@3		;got it
	test	bx,bx		;anything left?
	jnz	@@2
	jmp	short nomem	;no
@@3:	mov	ds:inbuf,ax	;save ptr
	mov	cl,4		;shift count
	sal	bx,cl		;get # bytes
	mov	ds:insiz,bx	;save
	mov	bx,bufsiz	;ask for output buf
@@4:	mov	ah,48h		;func=getblock
	int	21h
	jnc	@@7		;got it
	test	bx,bx		;anything left?
	jnz	@@4
	jmp	nomem		;no
@@5:	mov	dx,offset operr1 ;pt at msg
	mov	cx,loper1
	jmp	errmsg
@@6:	mov	dx,offset crerr1 ;pt at msg
	mov	cx,lcrer1
	jmp	errmsg
@@7:	mov	ds:outbuf,ax	;save ptr
	mov	cl,4		;shift count
	sal	bx,cl		;get # bytes
	mov	ds:outsiz,bx	;save
	mov	ds:outctr,bx	;that many bytes free
	xor	cx,cx		;nothing in buf now
	test	byte ptr ds:rvrsf,-1 ;going backwards?
	jnz	bwards		;yes
	;jmp	short fwards
;+
;
; Going through file forwards.
;
;-
fwards:	inc	cs:pgno		;page # +1
	call	check		;check page #
	jc	@@1		;don't print it
	call	prpage		;print the page
	jnc	fwards		;loop
	; page approved but is off eof, print a blank page if /BACKS and even
	test	byte ptr cs:backf,-1 ;doing /BACKS?
	jz	@@2
	test	byte ptr cs:pgno,1 ;yes, even page?
	jnz	@@2
	call	form		;yes, do a form feed
	jmp	short @@2
@@1:	call	skpage		;skip the page
	jnc	fwards
@@2:	jmp	done
;+
;
; Going through file backwards.
;
; First go through it forwards and record where each page begins, so we can
; go backwards w/o having to back up to the previous form feed for each page
; just to keep our registration correct.
;
;-
bwards:	; build a table of starting locs of all pages in the file
	mov	bx,-1		;see how much core is available
@@1:	mov	ah,48h		;func=getblock
	int	21h
	jnc	@@2		;success
	test	bx,bx		;anything left?
	jnz	@@1		;yes
	jmp	nomem		;no
@@2:	; init page table ptrs
	mov	cs:pagtab,ax	;save addr
	mov	cs:pagptr,0	;init ptr
	mov	cs:pagptr+2,ax
	mov	cs:pagtsz,bx	;and size
	add	bx,ax		;get ending addr
	mov	cs:pagmax,bx	;point at it
	xor	dx,dx		;initial col=0
	xor	si,si		;initial offset=0
@@3:	; see if we have space to store starting col and posn
	inc	cs:pgno		;bump page #
	mov	ax,cs:pagptr	;get pointer
	mov	bx,cs:pagptr+2
	mov	di,ax		;point at it
	mov	es,bx
	add	al,6		;+6 (3 words per page)
	cmp	al,10h		;off end of paragraph?
	jb	@@4
	and	al,0Fh		;yes
	inc	bx
@@4:	cmp	bx,cs:pagmax	;out of space?
	jb	@@6		;no
	ja	@@5		;yes
	test	ax,ax		;maybe, OK if we hit it exactly
	jz	@@6
@@5:	; too many pages
	mov	dx,offset tmpag2 ;pt at msg
	mov	cx,ltmpg2
	jmp	errmsg
@@6:	; we're OK, save file addr (dword) and starting column (word)
	mov	cs:pagptr,ax	;update
	mov	cs:pagptr+2,bx
	push	dx		;save starting column
	call	getptr		;get current pointer
	stosw			;save LSW
	mov	ax,dx		;MSW
	stosw
	pop	dx		;restore col
	mov	ax,dx		;copy it
	stosw
	call	skpage		;skip the page
	jnc	@@3		;loop if not EOF
	; delete starting addr of eof
	sub	cs:pagptr,6	;un-save last loc
	jnc	@@7
	add	cs:pagptr,10h	;keep ptr normalized
	dec	cs:pagptr+2
@@7:	; we're at end, find average length of page and figure out how much
	; we should overshoot when asked to SEEK, so if we're lucky we'll
	; get more than one page per bufferload
	mov	ax,cs:pos	;get position of EOF
	mov	dx,cs:pos+2
	mov	bx,cs:pgno	;get # pages
	dec	bx		;was too high by 1
	cmp	dx,bx		;will DIV overflow?  (>=64KB/page)
	jae	@@8		;yes, leave it at 0
	div	bx		;ax=average length of page
	add	ax,ax		;allow for twice the average
	jc	@@8		;that overflowed, leave at 0
	mov	cx,cs:insiz	;size of input buffer
	sub	cx,ax		;bite off 2x average page
	jc	@@8		;forget it, leave at 0
	mov	cs:sekoff,cx	;if anything left, overshoot by that much
@@8:	; start with blank page if /BACKS
	test	byte ptr cs:backf,-1 ;doing /BACKS?
	jz	@@9
	test	byte ptr cs:pgno,1 ;yes, even page?
	jnz	@@9
	call	check		;yes, not limited by /PAGES?
	jc	@@9
	call	form		;yes, do a form feed
@@9:	; go through pages backwards, checking each
	dec	cs:pgno		;back up to prev page
	jz	done		;0, nothing left
	sub	cs:pagptr,6	;back up ptr
	jnc	@@10
	add	cs:pagptr,10h	;whoops
	dec	cs:pagptr+2
@@10:	call	check		;should we print the page?
	jc	@@9		;no
	les	di,dword ptr cs:pagptr ;yes, point at page entry
	mov	ax,es:[di]	;get posn
	mov	dx,es:[di+2]
	call	seek		;fetch a buffer
	mov	dx,es:[di+4]	;get starting col
	call	prpage		;print the page
	jmp	short @@9
;+
;
; Finished printing, flush output buf and exit.
;
;-
done:	les	di,dword ptr cs:outptr ;load up ptr
	call	flush		;flush output buf
	mov	bx,cs:inhnd	;close input file
	mov	ah,3Eh		;func=close
	int	21h
;;; first see if output is to a file
	mov	bx,cs:outhnd	;close output file
	mov	ah,3Eh		;func=close
	int	21h


	int	20h
;+
;
; Check to see whether we should print page # CS:PGNO.
; Return CF=0 if so, CF=1 if not.
;
; si,cx	preserved
;
;-
check:	mov	ax,cs:pgno	;get page #
	test	byte ptr cs:frntf,-1 ;should we print fronts?
	jz	@@1		;no
	test	byte ptr cs:backf,-1 ;did they specify both?
	jnz	@@2		;yes, doesn't rule out much
	test	al,1		;is page number odd?  (CF=0)
	jz	@@5		;no, don't print
	ret
@@1:	test	byte ptr cs:backf,-1 ;did they specify neither?
	jz	@@2		;yes, so anything goes
	test	al,1		;is page number even?  (CF=0)
	jnz	@@5		;no, don't print
	ret
@@2:	; no /FRONTS or /BACKS (or both), check /PAGES list
	mov	dx,cs:rngctr	;get counter of # ranges
	test	dx,dx		;any?
	jz	@@6		;no, take everything
	mov	bx,offset rnglst ;pt at list
@@3:	cmp	ax,cs:[bx]	;in range?
	jb	@@4
	cmp	ax,cs:[bx+2]
	jbe	@@6		;yes
@@4:	add	bx,4		;no, advance to next
	dec	dx		;done all?
	jnz	@@3		;no
@@5:	stc			;don't print
	ret
@@6:	clc			;take it
	ret
;
swtchs	label	byte
	kw	<B-ACKS>,backs	;print only backs of pages
	kw	<E-VEN>,backs	;syn. for backs
	kw	<F-RONTS>,fronts ;print only fronts of pages
;	kw	<L-INES>,lines	;set # printable lines/page
	kw	<M-ARGIN>,margin ;set left margin
	kw	<O-DD>,fronts	;print only odd pages
	kw	<P-AGES>,pages	;print only a range of pages
	kw	<R-EVERSE>,revers ;print pages in reverse order
	kw	<W-RAP>,wrap	;wrap lines that go past right margin
	db	0
;+
;
; /BACKS
;
; Print only even-numbered pages (backs of two-sided sheets).
;
;-
backs:	mov	byte ptr ds:backf,1 ;set flag
	ret
;+
;
; /FRONTS
;
; Print only odd-numbered pages (fronts of two-sided sheets).
;
;-
fronts:	mov	byte ptr ds:frntf,1 ;set flag
	ret
;+
;
; /MARGIN:a[,b]
;
; Specify # of blanks to add at left margin.  If only "a" is specified, it is
; used for all pages;  if "a,b" are both specified then "a" is used for
; odd-numbered pages (fronts of two-sided sheets) and "b" is used for
; even-numbered pages (backs of two-sided sheets).
;
;-
margin:	jcxz	@@2		;nothing left, invalid
	lodsb			;eat a char
	dec	cx
	cmp	al,':'		;must be colon
	jne	@@2
	; get first number
	call	getdec		;get a number
	jc	@@2		;failed
	mov	ds:nblank,ax	;save
	mov	ds:nblank+2,ax	;in both slots
	jcxz	@@1		;nothing left
	cmp	byte ptr [si],',' ;comma?
	jne	@@1
	inc	si		;yes, eat it
	dec	cx
	call	getdec		;get second number
	jc	@@2		;failed
	mov	ds:nblank,ax	;replace # for even pages (backs)
@@1:	ret
@@2:	jmp	synerr
;+
;
; /PAGES:range1[,range2[,range3 ...]]
;
; Specify ranges of pages to be printed.  A range is either a single page
; number, or two page numbers separated by a hyphen (-).  Both this switch and
; the /FRONTS and /BACKS are used to decide whether a given page is printed, so
; for example if "/FRONTS/PAGES:1-5" is specified then pages 1, 3, and 5 are
; printed.
;
;-
pages:	sub	cx,1		;get colon
	jc	@@3		;none
	lodsb
	cmp	al,':'		;colon, right?
	jne	@@3		;error if not
@@1:	; get next element of range
	call	getdec		;get a number
	jc	@@3		;none, skip
	mov	bx,ax		;copy twice in case not range (start=end)
	jcxz	@@2		;just a number, skip
	cmp	byte ptr [si],'-' ;hyphen?
	jne	@@2		;no, just a number
	inc	si		;yes, eat it
	dec	cx
	push	ax		;save start of range
	call	getdec		;get end of range
	pop	bx		;[catch]
	jc	@@3		;failed
	cmp	ax,bx		;in order?
	jb	@@2
	xchg	ax,bx		;get them in order
@@2:	; ax=begn of range, bx=end, save them
	mov	di,ds:rngctr	;get ptr
	cmp	di,nrange	;already maxed out?
	je	@@4		;yes
	add	di,di		;*2
	add	di,di		;*4
	mov	ds:rnglst[di],ax ;save
	mov	ds:rnglst+2[di],bx
	inc	ds:rngctr	;bump ctr
	jcxz	@@5		;end of list
	cmp	byte ptr [si],',' ;comma?
	jne	@@5
	inc	si		;yes, eat it
	dec	cx
	jmp	short @@1	;around for more
@@3:	jmp	synerr		;syntax error
@@4:	mov	dx,offset tmpag1 ;pt at msg
	mov	cx,ltmpg1
	jmp	errmsg
@@5:	ret
;+
;
; /REVERSE
;
; Print pages in reverse order.  Useful for printing double-sided sheets in two
; passes, or for single-sided output on printers that stack backwards.  This
; can get confusing...
;
;-
revers:	mov	byte ptr ds:rvrsf,1 ;set flag
	ret
;+
;
; /WRAP
;
; Wrap text that overflows right margin.
;
;-
wrap:	mov	byte ptr ds:wrapf,1 ;set flag
	ret
;
	subttl	page I/O routines
;+
;
; Skip a page of text.
;
; ds:si	input ptr	\ updated
; cx	# bytes left	/ on return
; dx	col # at begn of page (updated for next page on return)
;
; Returns CF=1 if EOF (nothing read at all).
;
;-
skpage:	mov	di,cs:pglen	;get height
	test	cx,cx		;got anything?
	jnz	@@1
	call	read		;no, refill buf
	jcxz	@@5		;eof right off the bat
@@1:	; handle next char
	lodsb			;get a char
	cmp	al,40		;control char?
	jb	@@7		;yes
@@2:	inc	dx		;+1 column
@@3:	cmp	dx,cs:pgwid	;off right marg?
	jae	@@6		;yes
@@4:	loop	@@1		;loop around for more
	call	read		;get more
	jcxz	@@12		;eof
	jmp	short @@1	;loop around for more
@@5:	stc			;eof before reading anything
	ret
@@6:	; off right marg
	test	byte ptr cs:wrapf,-1 ;do we care about wrapping?
	jz	@@4		;no
	xor	dx,dx		;back to left marg
	jmp	short @@10	;pretend it was LF
@@7:	; control char
	cmp	al,bs		;backspace?
	je	@@8
	cmp	al,tab		;tab?
	je	@@9
	cmp	al,lf		;lf?
	je	@@10
	cmp	al,ff		;ff?
	je	@@11
	cmp	al,cr		;cr?
	je	@@13
	inc	dx		;anything else is just "^x"
	jmp	short @@2	;go add the other col
@@8:	; backspace
	sub	dx,1		;left a col
	jnc	@@4
	inc	dx		;stop at left marg
	jmp	short @@4
@@9:	; tab
	add	dx,8d		;ahead 8 cols
	and	dl,not 7	;back to stop
	jmp	short @@3
@@10:	; line feed
	dec	di		;count off a line
	jnz	@@4		;loop if not done
@@11:	; form feed
	dec	cx		;count it
@@12:	clc			;happy return
	ret
@@13:	; carriage return
	xor	dx,dx		;back to left marg
	jmp	short @@4
;+
;
; Print a page.
;
; ds:si	input ptr	\ updated
; cx	# bytes left	/ on return
; dx	col # at begn of page (updated for next page on return)
;
; Returns CF=1 if EOF (nothing read at all).
;
;-
prpage:	les	di,dword ptr cs:outptr ;get pointer
	mov	bp,cs:outctr	;get # bytes free
	test	dx,dx		;starting at left marg?
	jz	@@1
	mov	bx,dx		;no, copy
	call	blanks		;write enough blanks to get us there
@@1:	mov	bx,cs:pgno	;get page #
	and	bx,1		;isolate even/odd bit
	add	bx,bx		;*2
	mov	bx,cs:nblank[bx] ;look up # blanks to add at left marg
	mov	cs:padcnt,bx	;save (leave bx set up)
	mov	ax,cs:pglen	;get height
	mov	cs:lctr,ax	;init ctr
	test	cx,cx		;got anything?
	jnz	@@2
	call	read		;no, refill buf
	jcxz	@@8		;eof right off the bat
@@2:	; handle next char
	lodsb			;get a char
	cmp	al,40		;control char?
	jb	@@12		;yes
	; printing char, pad now if needed
	test	bx,bx		;already done?
	jnz	@@7		;no
@@3:	inc	dx		;+1 column
@@4:	cmp	dx,cs:pgwid	;off right marg?
	jae	@@10		;yes
@@5:	; save a char
	stosb			;save
	dec	bp		;count it
	jz	@@9
@@6:	loop	@@2		;loop around for more
	call	read		;get more
	test	cx,cx		;get anything?
	jnz	@@2		;yes
	jmp	@@23		;eof, print form feed
@@7:	; add padding
	push	ax		;save
	call	blanks		;write blanks, leaving bx=0 when done
	pop	ax		;restore
	jmp	short @@3
@@8:	stc			;eof before reading anything
	ret
@@9:	call	flush		;flush output buf
	jmp	short @@6	;continue
@@10:	; off right marg
	test	byte ptr cs:wrapf,-1 ;do we care about wrapping?
	jz	@@5		;no
@@11:	dec	si		;unget
	mov	al,cr		;write cr
	call	outchr
	xor	dx,dx		;back to col 0
	mov	bx,cs:padcnt	;reinit pad count
	mov	al,lf		;write lf
	call	outchr
	dec	cs:lctr		;count off a line
	jnz	@@2		;re-get char if not done
	jmp	@@23		;done, print form feed
@@12:	; control char
	cmp	al,bs		;backspace?
	je	@@15
	cmp	al,tab		;tab?
	je	@@18
	cmp	al,lf		;lf?
	je	@@21
	cmp	al,ff		;ff?
	je	@@22
	cmp	al,cr		;cr?
	je	@@16
	test	byte ptr cs:wrapf,-1 ;do we care if it wraps?
	jz	@@13		;no
	push	bx		;save
	mov	bx,dx		;copy
	inc	bx		;+2
	inc	bx
	cmp	bx,cs:pgwid	;off right marg?
	pop	bx		;[restore]
	jae	@@11		;yes, wrap and re-do char
@@13:	push	ax		;save
	; add padding if needed
	test	bx,bx		;any padding queued?
	jz	@@14
	 call	blanks		;write the blanks, leaving bx=0
@@14:	mov	al,'^'		;write a '^'
	call	outchr
	inc	dx		;count it
	pop	ax		;restore
	xor	al,100		;change to letter
	jmp	@@3		;go add the other col
@@15:	; backspace
	sub	dx,1		;left a col
	jnc	@@17
	inc	dx		;stop at left marg
	jmp	@@6
@@16:	jmp	short @@25
@@17:	jmp	@@5
@@18:	; tab
	mov	ax,dx		;save old value
	add	dx,8d		;ahead 8 cols
	and	dl,not 7	;back to stop
	test	byte ptr cs:wrapf,-1 ;do we care if it wraps?
	jz	@@19		;no
	cmp	dx,cs:pgwid	;off right marg?
	jae	@@20		;yes
@@19:	push	bx		;save
	mov	bx,dx		;copy new posn
	sub	bx,ax		;subtract old posn
	call	blanks		;write that many blanks
	pop	bx		;restore
	jmp	@@6
@@20:	mov	al,cr		;leave out the tab, just crlf
	call	outchr
	xor	dx,dx
	mov	bx,cs:padcnt	;reinit pad count
	mov	al,lf		;print LF
@@21:	; line feed
	dec	cs:lctr		;count off a line
	jnz	@@17		;loop if not done
@@22:	; form feed
	dec	cx		;count it
@@23:	test	dx,dx		;at BOL?
	jz	@@24
	mov	al,cr		;no, write a CR
	call	outchr
@@24:	mov	al,ff		;write an FF
	call	outchr
	mov	cs:outptr,di	;update
	mov	cs:outctr,bp
	clc			;happy return
	ret
@@25:	; carriage return
	xor	dx,dx		;back to left marg
	mov	bx,cs:padcnt	;reinit pad count
	jmp	@@5
;
blanks:	; write bx blanks (es:di set up), bx=0 on return
	mov	al,' '		;blank
@@1:	stosb			;write one
	dec	bp		;buf full?
	jz	@@2		;yes
	dec	bx		;count it
	jnz	@@1
	ret
@@2:	call	flush		;flush the buf
	dec	bx		;count the last blank
	jnz	blanks		;reload AL and continue unless done
	ret
;
form:	; write a form feed
	les	di,dword ptr cs:outptr ;get pointer
	mov	bp,cs:outctr	;get # bytes free
	mov	al,ff		;get form feed
	call	outchr		;write it
	mov	cs:outptr,di	;update
	mov	cs:outctr,bp
	ret
;
outchr:	; write a char
	stosb			;write it
	dec	bp		;buf full?
	jz	$+3
	 ret
	;callr	flush		;flush output buf
;+
;
; Flush output buffer.
;
; es:di	pointer into buffer
;
; bx,cx,dx,si,bp,ds  preserved
;
;-
flush:	push	bx		;save
	push	cx
	push	dx
	push	ds
	push	es		;copy es to ds
	pop	ds
	xor	dx,dx		;offset=0
	mov	cx,di		;length
	jcxz	@@1
	mov	bx,cs:outhnd	;handle
	mov	ah,40h		;func=write
	int	21h
	jc	wrerr
@@1:	pop	ds		;restore
	pop	dx
	pop	cx
	pop	bx
	xor	di,di		;point at begn
	mov	bp,ds:outsiz	;reinit ctr
	ret
wrerr:	mov	dx,offset wrerr1 ;pt at msg
	mov	cx,lwrer1
	jmp	errmsg
;
	subttl	file I/O routines
;+
;
; Get input file pointer.
;
; ds:si	current offset into buffer
; dx:ax	returns 32-bit file offset, suitable for SEEK
;
;-
getptr:	mov	ax,cs:pos	;get posn of base of buffer
	mov	dx,cs:pos+2
	add	ax,si		;add in offset
	adc	dx,0
	ret
;+
;
; Seek input file to a specified position and load a buffer.
;
; We try to be sneaky about seeks when we're reading the file backwards, since
; we don't want to have to fill the whole buffer for each new page and then
; waste most of it.  So, during the forward scan of the file, we calculated the
; average number of bytes per page.  Then we really seek to a location such
; that the desired address is at the end of the buffer, minus 2x the average
; page length.  That way the vast majority of pages will still lie entirely
; within the buffer we really load, plus if the pages aren't too big we will
; have the previous several pages already in the buffer for future searches.
;
; On entry:
; dx:ax	posn to go to
;
; On return:
; ds:si	buffer ptr
; cx	# bytes left
; es,di	preserved
;
;-
seek:	mov	bx,ax		;copy
	mov	cx,dx
	sub	bx,cs:pos	;subtract base of curr buf
	sbb	cx,cs:pos+2
	jnz	@@1		;>64KB away, or negative
	mov	cx,cs:inlen	;get end of buf
	sub	cx,bx		;find # to go after this one
	jbe	@@1		;none, need new buffer
	mov	si,bx		;yes
	mov	ds,cs:inbuf	;make sure we're pointing at buf
	ret
@@1:	; don't already have it, be sneaky if going backwards
	test	byte ptr cs:rvrsf,-1 ;going backwards?
	jz	@@3		;no, just do what they ask
	push	ax		;save
	push	dx
	sub	ax,cs:sekoff	;overshoot as much as is safe
	sbb	dx,0
	jnc	@@2
	xor	ax,ax		;whoops, stop at 0000:0000
	xor	dx,dx
@@2:	call	@@3		;seek to that address
	pop	dx		;restore the addr we really want
	pop	ax
	jmp	short seek	;will be within buf this time
@@3:	; decided on real address to use
	mov	cx,dx		;rearrange regs
	mov	dx,ax
	mov	bx,cs:inhnd	;handle
	mov	ax,4200h	;func=seek from BOF
	int	21h
	jc	rderr		;read error
	mov	cs:nxtpos,ax	;save
	mov	cs:nxtpos+2,dx
	;callr	read		;read buf, return
;+
;
; Load input buffer.
;
; On return:
;
; ds:si	pointer to buffer
; cx	# bytes at ds:si, 0 if EOF
; bx,dx,di,bp,es  preserved
;
;-
read:	push	bx		;save
	push	dx
	push	di
	push	cs		;copy cs to ds
	pop	ds
	mov	ax,ds:nxtpos	;get next position
	mov	dx,ds:nxtpos+2
	mov	ds:pos,ax	;it's current now
	mov	ds:pos+2,dx
	xor	dx,dx		;offset=0
	mov	cx,ds:insiz	;size of buffer
	mov	bx,ds:inhnd	;handle
	mov	ah,3Fh		;func=read
	mov	ds,ds:inbuf	;seg addr
	int	21h
	jc	rderr
	mov	si,dx		;point at it
	mov	cx,ax		;copy length
	mov	cs:inlen,ax	;save length
	add	cs:nxtpos,ax	;update posn for next time
	adc	cs:nxtpos+2,0
	pop	di		;restore
	pop	dx
	pop	bx
	ret
rderr:	mov	dx,offset rderr1 ;pt at msg
	mov	cx,lrder1
	jmp	errmsg
;
	subttl	parsing routines
;+
;
; Parse a decimal #.
;
; Returns:
; ax	number
; si,cx	updated (si pts at first non-digit)
; CF=1 if there was no number (ax=0)
;
;-
getdec:	xor	bx,bx		;init
	mov	di,10d		;multiplier
	call	skip		;skip white space
	jc	@@3		;nothing there, done
	push	si		;save
@@1:	lodsb			;get a char
	sub	al,'0'		;convert
	cmp	al,9d		;digit?
	ja	@@2		;no
	cbw			;ah=0
	xchg	ax,bx		;save, get old #
	mul	di		;*10
	test	dx,dx		;overflow?
	jnz	outran
	add	bx,ax		;add in new digit
	jc	outran
	loop	@@1		;loop
	inc	si		;compensate for below
@@2:	dec	si		;unget
	pop	di		;catch starting posn
	cmp	di,si		;see if we moved (di<si if so)
	cmc			;CF=1 if not
@@3:	mov	ax,bx		;copy
	ret
outran:	mov	dx,offset outr1	;pt at msg
	mov	cx,loutr1
	jmp	errmsg
;+
;
; Skip white space.
;
; CF=1 if eol, otherwise:
; al	first non-blank char
; si,cx	updated to point at char in al
;
;-
skip:	jcxz	@@2		;eol already
@@1:	lodsb			;get a byte
	cmp	al,' '		;blank or ctrl char?
	ja	@@3		;no, stop (CF=0)
	loop	@@1		;loop
@@2:	stc			;hit eol
	ret
@@3:	dec	si		;unget
	ret
;+
;
; Parse a word from the input line.
;
; ds:si	current position
; cx	# chars left
;
; On return:
; si	points at posn after last char of word
; cx	updated
; bx	points at begn of word if CF=0
; dx	length of word
;
;-
getw:	jcxz	@@2		;eol already
@@1:	; look for beginning of word
	mov	bx,si		;in case word starts here
	lodsb			;get a char
	cmp	al,' '		;blank or ctrl?
	ja	@@4		;no
	loop	@@1		;loop
@@2:	stc			;no luck
	ret
@@3:	; look for end of word
	lodsb			;get a char
@@4:	cmp	al,' '		;blank or ctrl?
	jbe	@@6		;yes, end of word
	cmp	al,ds:swchar	;switch?
	je	@@6
	cmp	al,'='		;value?
	je	@@6
	cmp	al,':'
	je	@@6
	cmp	al,'a'		;lower case?
	jb	@@5
	cmp	al,'z'		;hm?
	ja	@@5
	and	al,not 40	;yes, convert
	mov	[si-1],al	;put back
@@5:	loop	@@3		;loop
	inc	si		;compensate for next inst
@@6:	dec	si		;unget
	mov	dx,si		;calc length
	sub	dx,bx		;CF=0
	ret
;+
;
; Look up a keyword in a table.
;
; ds:bx	keyword } from GETW
; dx	length  }
; ss:ax	table
;
; Returns CF=1 if not found, otherwise ax=number from table, di=HELP ptr.
;
; This routine doesn't require that DS=CS, so it may be used to parse
; environment strings.
;
; si,cx preserved either way.
;
;-
tbluk:	push	cx		;save
	push	si
	push	ds
	mov	si,ax		;pt at table
	push	ds		;copy ds to es
	pop	es
	push	ss		;and ss to ds
	pop	ds
	xor	ch,ch		;ch=0
@@1:	lodsw			;get length,,length to match
	test	al,al		;end?
	jz	@@4
	mov	cl,ah		;assume bad length
	cmp	al,dl		;is ours long enough?
	ja	@@2		;no
	sub	ah,dl		;too long?
	jc	@@2		;yes
	mov	cl,dl		;just right
	mov	di,bx		;point at keyword
	repe	cmpsb		;match?
	je	@@3
	add	cl,ah		;no, add extra length
@@2:	add	si,cx		;skip to end
	lodsw			;skip jump addr
	jmp	short @@1	;loop
@@3:	; got it
	mov	cl,ah		;get extra length
	add	si,cx		;skip to end
	lodsw			;get dispatch addr
	stc			;makes CF=0 below
@@4:	; not found
	cmc			;CF=-CF
	pop	ds		;restore regs
	pop	si
	pop	cx
	ret
;
	subttl	pure data
;
nomem1	db	'?Insufficient memory',cr,lf
lnmem1=	$-nomem
;
rderr1	db	'?Read error',cr,lf
lrder1=	$-rderr1
;
wrerr1	db	'?Write error',cr,lf
lwrer1=	$-wrerr1
;
outr1	db	'?Number out of range',cr,lf
loutr1=	$-outr1
;
syerr1	db	'?Syntax error',cr,lf
lsyer1=	$-syerr1
;
operr1	db	'?Input file open error',cr,lf
loper1=	$-operr1
;
crerr1	db	'?Output file creation error',cr,lf
lcrer1=	$-crerr1
;
tmpag1	db	'?Too many /PAGES ranges',cr,lf
ltmpg1=	$-tmpag1
;
tmpag2	db	'?Too many pages for /REVERSE',cr,lf
ltmpg2=	$-tmpag2
;
	subttl	impure data
;
usage	label	byte
	db	'PAGE V1.0  by John Wilson <wilson@dbit.dbit.com>',cr,lf
	db	'page [infile [outfile]] ~switches',cr,lf
	db	'  ~FRONTS (syn. ~ODD)',cr,lf
	db	'  ~BACKS (syn. ~EVEN)',cr,lf
	db	'  ~MARGIN:fronts[,backs]',cr,lf
	db	'  ~PAGES:a-b,c,d-e',cr,lf
	db	'  ~REVERSE',cr,lf
	db	'  ~WRAP',cr,lf
lusage=	$-usage
;
inmlen	db	0		;length of input filename or 0 if none
onmlen	db	0		;length of output filename or 0 if none
;
inhnd	dw	0000h		;input file handle, default=STDIN
outhnd	dw	0001h		;output file handle, default=STDOUT
;
pos	dw	0,0		;location of base of input buffer
nxtpos	dw	0,0		;position of begn of next READ
pgno	dw	0		;current page number
sekoff	dw	0		;amount to over-seek when going backwards
;
rvrsf	db	0		;NZ => print pages in reverse
frntf	db	0		;NZ => print fronts of pages
backf	db	0		;NZ => print backs of pages
wrapf	db	0		;NZ => wrap long lines
pgwid	dw	80d		;# cols per page
pglen	dw	60d		;# printable lines per page
nblank	dw	0,0		;# blanks to add at left of even, odd pages
;
outptr	dw	0		;ptr into output buf
outbuf	dw	1 dup(?)	;seg addr of output buf (MUST FOLLOW OUTPTR)
outctr	dw	1 dup(?)	;# bytes free in OUTBUF
;
rngctr	dw	0		;# entries in RNGLST
;
	subttl	pure storage
;
swchar	db	1 dup(?)	;SWITCHAR
iname	dw	1 dup(?)	;ptr to input filename in cmd line
oname	dw	1 dup(?)	;ptr to output filename in cmd line
inbuf	dw	1 dup(?)	;seg addr of input buf
insiz	dw	1 dup(?)	;size in bytes of INBUF
outsiz	dw	1 dup(?)	;size in bytes of OUTBUF
;
fnbuf	label	byte		;filename buf, overlays RNGLST
rnglst	dw	2*nrange dup(?)	;start, end page # for each range
if ($-fnbuf) lt 80h		;must be big enough for cmd line +0
	db	(80h-($-fnbuf)) dup(?)
endif
;
pagtab	dw	1 dup(?)	;page table base (seg addr)
pagptr	dw	2 dup(?)	;current addr in page table
pagtsz	dw	1 dup(?)	;page table size (in paragraphs)
pagmax	dw	1 dup(?)	;seg addr following end of table
;
inlen	dw	1 dup(?)	;# bytes currently in INBUF
;
lctr	dw	1 dup(?)	;line counter in PRPAGE
padcnt	dw	1 dup(?)	;# blanks to add in PRPAGE
;
	dw	100h dup(?)	;stack goes here
if (($-start) and 0Fh)
	db	(10h-(($-start) and 0Fh)) dup(?)
endif
pdl	label	word		;SP
code	ends
	end	start
