	title	like, do some lines, man
;++
;
; Count the lines or pages in files.
;
; By John Wilson.
;
; 01/10/1988	JMBW	Created.
; 02/17/1995	JMBW	Accepts wildcards as cmd line args, default=STDIN.
;			Converted to .COM file for smaller size.
; 07/03/1996	JMBW	Optionally counts pages instead.
; 04/03/2002	JMBW	Added support for Windows 9x long filename calls.
;
;--
	.radix	8
;
lf=	12
ff=	14
cr=	15
;
lfnlen=	13Eh			;length of LFN "finddata" record
;
code	segment
	assume	cs:code
	org	100h		;.COM file
;
beg:	jmp	short beg1
	; put program name here so that "TYPE LINES.COM" gives something
	db	10,10		;(two backspaces to cover the JMP SHORT)
	db	'LINES.COM, by John Wilson <wilson@dbit.com>',cr,lf
	db	'Z'-100		;EOF for TYPE
beg1:	cld			;DF=0
	; get DOS version
	mov	ah,30h		;func=get DOS ver
	int	21h
	cmp	al,2		;we need V2.0 or later
	jb	beg4
	xchg	al,ah		;>< to be right way around
	mov	ds:dosver,ax	;save it
	; give back memory we don't need
	cmp	sp,offset pdl	;make sure we have it in the first place
	jb	beg3
	mov	sp,offset pdl	;shrink stack if so
	mov	bx,mem		;size of memory to save in paragraphs
	mov	ah,4Ah		;func=setblock
	int	21h
	jc	beg3		;punt on failure, can't reorganize memory
	; allocate buffer
	mov	bx,0FFFh	;size=64KB -1 par (REPNE can't reach all 64K)
beg2:	mov	ah,48h		;func=getblock
	int	21h
	jnc	beg5		;got it, skip
	mov	bx,ax		;copy amount DOS says it can give
	test	bx,bx		;anything?
	jnz	beg2		;try again if so
beg3:	; failed, punt
	mov	dx,offset nomem	;pt at msg
	mov	cx,21d		;length
	mov	bx,0002h	;STDERR
	mov	ah,40h		;func=write
	int	21h
	mov	ax,4C01h	;func=punt
	int	21h
beg4:	mov	dx,offset olddos ;pt at msg
	mov	dx,09h		;func=print
	int	21h
	int	20h		;exit, DOS V1.x style
beg5:	; got it, copy cmd line out of DTA
	mov	ds:buf,ax	;save buf ptr
	mov	cl,4		;shift count
	sal	bx,cl		;get # bytes in buf
	mov	ds:buflen,bx	;save
	mov	si,80h		;pt at JCL
	lodsb			;get length
	and	ax,7Fh		;should be <= 127., well it is now
	mov	cx,ax		;copy
	mov	di,offset jcl	;pt at cmd line buffer
	push	di		;save ptr
	rep	movsb		;copy it there
	pop	si		;point at it
	mov	cx,ax		;restore length
prs1:	; parse out each word and look it up as a wildcard
	jcxz	prs3		;skip if nothing to do
prs2:	lodsb			;get a byte
	cmp	al,' '		;blank or ctrl char?
	ja	prs6		;no
	loop	prs2		;ignore if so
prs3:	; end of command line
	mov	di,offset lbuf	;pt at LBUF
	cmp	ds:nfiles,1	;have we gotten anything?
	jb	prs5		;no, read STDIN
	je	prs4		;exactly one thing, done
	; more than one file, print total
	mov	si,offset total	;pt at string
	mov	cx,7		;LEN('Total: ')
	rep	movsb		;copy
	mov	ax,ds:totlin	;get total # lines
	mov	dx,ds:totlin+2
	call	prline		;finish off line
prs4:	int	20h
prs5:	; no files given, read standard input
	mov	si,offset stdin	;pt at string
	mov	cx,9d		;LEN('(stdin): ')
	rep	movsb		;copy
	call	ds:count	;count it up, print result
	int	20h
prs6:	; beginning of a word
	mov	di,offset lbuf	;point at LBUF
	mov	dx,di		;copy ptr
	mov	ah,al		;assume delimiter
	cmp	al,'"'		;quote?
	je	prs14
	cmp	al,"'"		;(either kind)
	je	prs14		;eat it if so
	xor	ah,ah		;otherwise no delimiter
	dec	si		;back up
prs7:	; get next char
	lodsb			;get a char
	test	ah,ah		;do we have a delimiter?
	jnz	prs9		;yes, go check it
	cmp	al,' '		;blank or ctrl char?
	jbe	prs16		;yes, end of word
prs8:	cmp	al,':'		;colon?
	je	prs10
	cmp	al,'/'		;either kind of slash?
	je	prs12
	cmp	al,'\'
	je	prs11
	cmp	al,'a'		;lower case?
	jb	prs13
	cmp	al,'z'
	ja	prs13
	sub	al,40		;convert to upper if so
	jmp	short prs13
prs9:	; string was quoted (may be LFN containing blank(s)), check delimiter
	cmp	al,ah		;we had a starting delimiter, is this it again?
	jne	prs8		;no, back in
	dec	cx		;yes, eat it
	jmp	short prs15	;continue
prs10:	cmp	dx,offset lbuf	;have we gotten a path already?
	je	prs12		;no, "x:" may be it
	jmp	short prs13	;otherwise just save it and continue
prs11:	mov	al,'/'		;normalize to forward slash
prs12:	lea	dx,[di+1]	;point after it
prs13:	stosb			;save
prs14:	loop	prs7		;loop
prs15:	inc	si		;correct for next
prs16:	dec	si		;unget last char
	; SI,CX=cmd line descriptor, DI points past end of filespec in LBUF
	; DX points at beginning of filename in filespec in LBUF
	cmp	di,offset lbuf+2 ;got exactly 2 bytes?
	jne	prs18		;no
	; 2-byte filename, see if it's really a command
	mov	ax,word ptr ds:lbuf ;get it
	mov	bx,offset lines	;assume counting lines
	cmp	ax,"L-"		;-L?  (/L is weird because it could be \L)
	je	prs17
	mov	bx,offset pages	;assume counting pages
	cmp	ax,"P-"		;-P?
	jne	prs18		;no
prs17:	mov	ds:count,bx	;save ptr
	jmp	prs1		;get next filename
prs18:	; look up the filename as a wildcard (in case it is)
	push	si		;save
	push	cx
	push	dx		;save filename ptr
	xor	al,al		;mark end, AL=0 for below
	stosb
	dec	di		;back up in case of error
	; check for long filename support (Windows 9x)
	mov	ds:lfn,al	;assume no LFN support (AL=0 from above)
	cmp	byte ptr ds:dosver+1,4 ;V4.0 or later?
	jb	prs21		;no, INT 21h/AH=6Ch didn't exist anyway
	mov	ax,word ptr ds:lbuf ;get first two letters of filespec
	cmp	ah,':'		;drive?
	jne	prs19
	and	al,not 40	;yes, convert to upper case
	sub	al,'A'		;should be letter A-Z
	cmp	al,'Z'-'A'	;right?
	jbe	prs20		;yes, convert it back
prs19:	mov	ah,19h		;func=get logged in disk
	int	21h		;(no error possible)
prs20:	add	al,'A'		;convert to letter
	mov	byte ptr ds:rtnam,al ;save
	push	di		;save
	mov	dx,offset rtnam	;point at it
	mov	di,offset lfnbuf ;point at scratch buffer
	mov	cx,lfnlen	;length in bytes
	stc			;(in case old DOS)
	mov	ax,71A0h	;func=get LFN volume information
	int	21h
	pop	di		;[restore]
	jc	prs22
	and	bh,100		;isolate LFN bit
	jz	prs22		;it's clear, skip
	mov	ds:lfn,bh	;save it
prs21:	; LFN support found, use LFN calls to find first match
	push	di		;save in case needed for err msg
	mov	di,offset lfnbuf ;ES:DI=FindData buf
	mov	si,1		;use DOS date/time words
	mov	dx,offset lbuf	;pt at begn
	xor	cx,cx		;no special attrs
	mov	ax,714Eh	;func=LFN find first match
	int	21h
	pop	di		;[restore]
	jc	prs25		;failed
	mov	ds:ffhand,ax	;save FileFind handle
	jmp	short prs23
prs22:	; search for file using regular DOS calls
	mov	dx,offset lbuf	;pt at begn
	xor	cx,cx		;attrib=generic file
	mov	ah,4Eh		;func=find first
	int	21h
	jc	prs25		;error, no matches
prs23:	; match found
	pop	di		;get ptr to filename in LBUF
	push	di		;save again
	mov	si,offset lfnbuf+2Ch ;point at long filename
	test	byte ptr ds:lfn,-1 ;is it an LFN file?
	jnz	prs24		;yes
	 mov	si,9Eh		;pt at filename in default DTA
prs24:	lodsb			;get a byte
	stosb			;save
	test	al,al		;end?
	jnz	prs24		;loop if not  (leave AL=0 if so)
	dec	di		;back up
	; open the file
	cmp	ds:lfn,al	;LFN file?  (AL=0 from above)
	jnz	short prs26
	; (DOS open)
	mov	dx,offset lbuf	;pt at filename
	mov	ax,3D00h	;func=open /RONLY
	int	21h		;open it
	jmp	short prs27	;(go check status)
prs25:	; file not found
	mov	si,offset fnf	;pt at msg
	mov	cx,18d		;length
	jmp	short prs31
prs26:	; (LFN open)
	push	di		;save
	xor	di,di		;alias hint (not used)
	mov	si,offset lbuf	;pt at filename
	mov	dx,01h		;action:  FNF=fail, exists=open
	xor	cx,cx		;attr=default (not used)
	xor	bx,bx		;flags=none, mode=/RONLY
	mov	ax,716Ch	;func=LFN open/whatever
	int	21h
	pop	di		;[restore]
prs27:	jc	prs30		;open failed
	mov	ds:handle,ax	;save
	mov	ax," :"		;': '
	stosw
	call	ds:count	;count up this file
	mov	bx,ds:handle	;get handle
	mov	ah,3Eh		;func=close
	int	21h
	; find next match
	test	byte ptr ds:lfn,-1 ;is this an LFN file?
	jz	prs28		;no
	; find next match using LFN call
	mov	di,offset lfnbuf ;ES:DI=FindData buf
	mov	si,1		;use DOS date/time words
	mov	bx,ds:ffhand	;FileFind handle
	mov	ax,714Fh	;func=LFN find next match
	int	21h
	jnc	prs23		;loop if got another
	mov	ax,71A1h	;func=close FileFind handle
	int	21h
	jmp	short prs29
prs28:	; find next match using DOS call
	mov	dx,80h		;point at default PSP (not in V2, MS geeks!!!)
	mov	ah,4Fh		;func=find next
	int	21h
	jnc	prs23		;loop if got another
prs29:	pop	di		;flush filename ptr once and for all
	pop	cx		;restore cmd line ptr
	pop	si
	jmp	prs1
prs30:	; open error
	mov	si,offset opnerr ;pt at msg
	mov	cx,19d		;length
prs31:	rep	movsb		;copy msg
	mov	dx,offset lbuf	;pt at buf
	mov	cx,di		;calc length
	sub	cx,dx
	mov	bx,0002h	;STDERR
	mov	ah,40h		;func=write
	int	21h
	mov	ax,4C01h	;func=punt
	int	21h
;+
;
; Count and display number of pages in a file.
;
; File handle is in CS:HANDLE.
;
; ds,es	preserved
; es:di	pointer into LBUF for decimal number
;
;-
pages:	push	di		;save
	push	ds
	push	es
	inc	ds:nfiles	;count it
	mov	ds:locnt,0	;nuke low word of page count
	mov	ds:hicnt,0	;nuke high word
	mov	ax,ds:buf	;pt at buf
	mov	ds,ax		;with ds
	mov	es,ax		;and es
	mov	si,cs:linpag	;# lines printed per page
pag1:	; read a bufferload
	xor	dx,dx		;offset=0
	mov	cx,cs:buflen	;length
	mov	bx,cs:handle	;file handle
	mov	ah,3Fh		;func=read
	int	21h		;do it
	jc	pag7		;err, treat as eof
	mov	cx,ax		;copy count
	jcxz	pag7		;eof, skip
	xor	di,di		;start of buf
pag2:	; first look for the closest form feed in the buffer
	mov	bx,cx		;save
	mov	dx,di
	mov	al,ff		;form feed
	repne	scasb		;look for one
	mov	cx,bx		;restore count
	mov	bx,di		;point at it, or past EOB if none
	mov	di,dx		;rewind ptr
	mov	al,lf		;line feed
pag3:	repne	scasb		;look for it
	jne	pag6		;end of buf, see if we know of a form feed
	cmp	di,bx		;have we passed the known FF (if any)?
	ja	pag5		;yes, process that first
	dec	si		;otherwise count it
	jnz	pag4		;look again
	add	cs:locnt,1	;count it
	adc	cs:hicnt,0
	mov	si,cs:linpag	;reload # lines/page
pag4:	test	cx,cx		;done all?
	jnz	pag3		;loop if not
	jmp	short pag6	;keep it up
pag5:	; found an LF but it was after a FF
	sub	di,bx		;find # by which we passed the FF
	add	cx,di		;fix count
	mov	di,bx		;to point one character past FF
	add	cs:locnt,1	;count the page
	adc	cs:hicnt,0
	mov	si,cs:linpag	;reload # lines/page
	jmp	short pag2	;find a new FF and then look for the LF again
pag6:	; hit end of buffer with no LFs, see if we know of an FF
	cmp	di,bx		;did we know of an FF up ahead?
	je	pag1		;no, load next buf
	add	cs:locnt,1	;count the page
	adc	cs:hicnt,0
	mov	si,cs:linpag	;reload # lines/page
	jmp	short pag1	;load next buffer
pag7:	; display count
	pop	es		;restore
	pop	ds
	pop	di
	mov	ax,ds:locnt	;copy
	mov	dx,ds:hicnt
	; see if we had a a partial page
	; (N.B. will fail if they wrote stuff on the first line of the page but
	; had no LF, probably not worth worrying about)
	cmp	si,cs:linpag	;done anything on this page?
	je	pag8		;no
	add	ax,1		;count an extra page if so
	adc	dx,0
pag8:	add	ds:totlin,ax	;add to total
	adc	ds:totlin+2,dx
	jmp	short prline	;print line, return
;+
;
; Count and display number of line feeds in a file.
;
; File handle is in CS:HANDLE.
;
; ds,es	preserved
; es:di	pointer into LBUF for decimal number
;
;-
lines:	push	di		;save
	push	ds
	push	es
	inc	ds:nfiles	;count it
	mov	ds:hicnt,0	;nuke high word of line count
	mov	ax,ds:buf	;pt at buf
	mov	ds,ax		;with ds
	mov	es,ax		;and es
	xor	si,si		;init low word of line count
lin1:	; read a bufferload
	xor	dx,dx		;offset=0
	mov	cx,cs:buflen	;length
	mov	bx,cs:handle	;file handle
	mov	ah,3Fh		;func=read
	int	21h		;do it
	jc	lin4		;err, treat as eof
	mov	cx,ax		;copy count
	jcxz	lin4		;eof, skip
	xor	di,di		;start of buf
	mov	al,lf		;line feed
lin2:	repne	scasb		;look for it
	jne	lin1		;end of buf, load next
	inc	si		;count it
	jz	lin3		;overflow to high word
	test	cx,cx		;is there more?
	jnz	lin2		;look again if so
	jmp	short lin1
lin3:	inc	cs:hicnt	;bump high count
	test	cx,cx		;more?
	jnz	lin2		;loop if so
	jmp	short lin1
lin4:	; display count
	pop	es		;restore
	pop	ds
	pop	di
	mov	ax,si		;copy
	mov	dx,ds:hicnt
	add	ds:totlin,ax	;add to total
	adc	ds:totlin+2,dx
	;jmp	short prline	;print line, return
;+
;
; Add a number to LBUF and print the whole line.
;
; dx:ax	number
; es:di	ptr to end of line in LBUF
;
;-
prline:	call	prdec		;print # in decimal
	mov	ax,cr+(lf*400)	;crlf
	stosw
	mov	dx,offset lbuf	;pt at buf
	mov	cx,di		;calc length
	sub	cx,dx
	mov	bx,0001h	;STDOUT
	mov	ah,40h		;func=write
	int	21h
	ret
;+
;
; Print decimal # in DX:AX.
;
; es:di	buffer (updated on return)
;
;-
prdec:	mov	bx,10d		;divisor
prdec1:	xor	cx,cx		;assume high order result=0 (no overflow)
	cmp	dx,bx		;number .lt. divisor*65536?
	jb	prdec2		;yes, a single DIV will do it
	; result won't fit in 16 bits (quo.lt.65536, rem.lt.BX)
	; do the division in two steps so as to get correct low order
	mov	cx,ax		;save low order
	mov	ax,dx		;copy high order
	xor	dx,dx		;0-extend
	div	bx		;do first 16-bit "digit" of long division
	xchg	ax,cx		;save high "digit" of result, get low order
prdec2:	div	bx		;(DX is remainder "carried" from first DIV)
	; CX:AX=quotient, DX=remainder
	push	dx		;save remainder
	mov	dx,cx		;copy
	or	cx,ax		;should we recurse?
	jz	prdec3
	 call	prdec1		;yes
prdec3:	pop	ax		;restore remainder
	or	al,'0'		;convert to decimal
	stosb			;save
	ret
;
olddos	db	'Incompatible DOS version',cr,lf,'$'
nomem	db	'Insufficient memory',cr,lf
total	db	'Total: '
stdin	db	'(stdin): '
fnf	db	': File not found',cr,lf
opnerr	db	': File open error',cr,lf
;
totlin	dw	0,0		;total # lines
nfiles	dw	0		;# files processed so far
;
count	dw	lines		;routine to do the counting
;
linpag	dw	60d		;# printed lines per page
;
handle	dw	0000h		;file handle (STDIN unless changed)
;
rtnam	db	'x:\',0		;name of root directory ("x" is patched out)
;
dosver	dw	1 dup(?)	;major,,minor DOS version
;
lfn	db	1 dup(?)	;NZ => long filenames work for this filespec
ffhand	dw	1 dup(?)	;FileFind handle
;
buf	dw	1 dup(?)	;seg addr of file buffer
buflen	dw	1 dup(?)	;length in bytes of file buffer
;
locnt	dw	1 dup(?)	;low word of # pages in current file
hicnt	dw	1 dup(?)	;high word of # pages/lines in current file
;
jcl	db	127d dup(?)	;copy of JCL (moved out of DTA for FINDNEXT)
;
lbuf	db	255d+2 dup(?)	;output line buffer
				;(bigger than max possible command line)
;
lfnbuf	db	lfnlen dup(?)	;buffer for LFN vol data and finddata record
;
	dw	100h dup(?)	;stack
pdl	label	word
;
mem=	((($+0Fh)-beg)/10h)+10h	;# paragraphs to save, including 10h for PSP
;
code	ends
;
	end	beg
