	.title	find -- find patterns in text
 
	.enabl	lc
 
; Search for patterns in text files
;
; D. G. Conroy	Sept 7, 1977
 
	.globl	$main
 
	.mcall	call,	return,	get$s,	put$s
	.mcall	fsrsz$,	fdbdf$,	fdop$a,	ofnb$r
	.mcall	close$,	csi$
 
	fsrsz$	3
	csi$
 
; Equivalences
 
RBSIZE	=	120.		;Size of record buffer
PBSIZE	=	100.		;Size of pattern buffer
LBSIZE	=	8.		;Size of line number buffer
FBSIZE	=	40.		;Size of file name buffer
 
; Pattern matcher opcodes. The order is critical
; but may be changed if the jump table in amatch is
; changed also.
 
END	=	0		;End of pattern
CHAR	=	2		;Character
BOL	=	4		;Beginning of line
EOL	=	6		;End of line
ANY	=	10		;Any character
CCL	=	12		;Character class
NCCL	=	14		;Negated character class
CLO	=	16		;Closure
 
; Local data
 
fdb:	fdbdf$			;Fdb for non stdin files
	fdop$a	4
count:	.word	0		;Count of matches
lno:	.blkw	1		;Line number
fflag:	.byte	0		;-f (filename) flag
nflag:	.byte	0		;-n (line numbers) flag
nfflag:	.blkb	1		;Flag for printing file name
rflag:	.byte	0		;-r (reverse) flag
cflag:	.byte	0		;-c (count) flag
lbuf:	.blkb	LBSIZE		;Must be just before rbuf!!!
rbuf:	.blkb	RBSIZE		;Working record buffer
pbuf:	.blkb	PBSIZE		;Pattern buffer
fmsg:	.ascii	"File "		;Message for -f option
fmsgf:	.blkb	FBSIZE
 
; Ascii things
 
msg01:	.asciz	"x: illegal option"
msg02:	.asciz	"Illegal pattern"
msg03:	.asciz	"Usage: find [-r] [-c] [-f] [-n] pattern [file ...]"
msg04:	.asciz	": not found"
 
	.even
 
;+
; ** $main -- mainline
;
; This routine receives control from the standard setup
;-
 
$main:	mov	$argc,r3	;Get arg. count
	mov	#$argv,r4	;Pointer into args.
	clr	r5		;Number of file args.
 
10$:	dec	r3		;Any more args.
	ble	50$		;Br if not
	tst	(r4)+		;Position to the next arg.
	mov	(r4),r1		;Get pointer to arg
 
	cmpb	(r1),#'-	;Is it an option
	bne	40$		;Br if not
	inc	r1		;Skip the '-'
20$:	movb	(r1)+,r2	;Pickup option character
	beq	30$		;End of list
	call	maplow		;Make lower case (for match)
	cmpb	r2,#'c		;-c
	bne	22$		;No
	incb	cflag		;Yes, set count flag
	br	20$		;Next
22$:	cmpb	r2,#'r		;-r
	bne	24$		;No
	incb	rflag		;Yes, set reverse flag
	br	20$		;Next
24$:	cmpb	r2,#'f		;-f
	bne	26$		;No
	incb	fflag		;Yes, set file name flag
	br	20$		;Next
26$:	cmpb	r2,#'n		;-n
	bne	28$		;No
	incb	nflag		;Yes, set line number flag
	br	20$		;Next
28$:	movb	r2,msg01	;Option is illegal
	mov	#msg01,r0	;Type out
	call	$msg		;Diagnostic
	br	20$		;Get next option
30$:	clr	(r4)		;Kill this argv entry and
	br	10$		;Continue the argv scan
 
40$:	inc	r5		;Count a file argument
	cmp	r5,#1		;Is this the first one (pattern)
	bne	10$		;Br if not
	clr	(r4)		;Kill this argv entry
	mov	r1,r0		;Compile the
	call	compil		;Pattern
	bcc	10$		;If ok, continue
	mov	#msg02,r0	;Otherwise complain about the pattern
	call	$msg		;And
	br	100$		;Exit
 
50$:	dec	r5		;Test if enough arguments
	bpl	60$		;Enough
	mov	#msg03,r0	;Display usage message and
	call	$msg		;Then
	br	100$		;Exit
 
60$:	bne	70$		;Br if files in the argv
	mov	#$stdin,r0	;Search the
	call	finder		;Standard input
	br	90$		;Done
 
70$:	mov	#$argv,r4	;Working argv pointer
80$:	dec	$argc		;Any more
	ble	90$		;Br if not
	tst	(r4)+		;Get next
	mov	(r4),r0		;File name
	beq	80$		;Killed entry
	call	$mdspt		;Build a dspt
	bcs	85$		;Bad file name (or something)
 
	mov	#fdb,r0		;Parse the file name
	mov	#fdb+f.fnb,r1
	mov	#$csibk+c.dsds,r2
	mov	#$dfnb,r3
	call	.parse
	bcs	85$		;Bad file name
	bit	#nb.svr+nb.stp+nb.snm,n.stat(r1)
	bne	82$		;Br on a wildcard
 
	ofnb$r			;Open the file
	bcs	85$		;Cannot open the file
	call	finder		;Search it
	close$			;Close it
	br	80$		;And go get another
 
82$:	bit	#nb.ver,n.stat(r1) ;Is there an explicit version
	bne	83$		;Br if yes
	bis	#nb.svr,n.stat(r1) ;Set ;*
83$:	call	.find		;Look for a file
	bcs	80$		;Error, assume no more files
	ofnb$r			;Open the file
	bcs	85$		;This should never happen
	call	finder		;Search it
	close$			;Close it
	br	83$		;And try to find another
 
85$:	mov	#msg04,r0	;: not found
	mov	(r4),r1		;The file name
	call	$fmsg		;Report the error
	br	80$		;And continue
 
90$:	tstb	cflag		;-c
	beq	100$		;No, just leave
	mov	count,r0	;Write out the count
	mov	#rbuf,r1
	call	$itoc
	sub	#rbuf,r1
	put$s	#$stdou,#rbuf,r1
 
100$:	return			;Done
 
;+
; ** finder -- perform a find on a file
;
; Inputs:
;	r0=pointer to fdb of file
;	pbuf=pattern to match
;
; Outputs:
;	Matching records are written to standard output
;-
 
finder:	mov	r0,-(sp)	;Save things
	mov	r1,-(sp)
	mov	r2,-(sp)
	mov	r5,-(sp)
 
	mov	r0,r5		;Get fdb pointer in a safe place
	clrb	nfflag		;Is the file message being printed
	tstb	fflag		;Only print if -f
	beq	10$
	cmp	r5,#$stdin	;And not standard input
	beq	10$
	incb	nfflag		;Need a name
10$:	clr	lno		;Clear out line number
 
20$:	get$s	r5,#rbuf,#RBSIZE;Get a record
	bcs	90$		;Error, assume eof
	inc	lno		;Update line number
 
	mov	f.nrbd(r5),r0	;Make the record asciz
	clrb	rbuf(r0)
	tstb	rflag		;Reverse sense search
	bne	30$		;Br if yes
	call	amatch		;Go back if
	bcs	20$		;No match
	br	40$
30$:	call	amatch		;Go back if
	bcc	20$		;Match
 
40$:	inc	count		;Count a match
	tstb	cflag		;-c
	bne	20$		;Yes, don't print
 
	tstb	nfflag		;Need a file name
	beq	50$		;Br if not
	call	filnam		;Put out the file name
	clrb	nfflag
 
50$:	tstb	nflag		;Line numbers
	bne	60$		;Br if yes
	put$s	#$stdou,#rbuf,f.nrbd(r5) ;Write to standard output
	br	20$
 
60$:	mov	lno,r0		;Convert line number
	mov	#lbuf,r1
	call	$itoc
	movb	#':,(r1)+	;For looks
70$:	cmp	r1,#rbuf	;Pad up with blanks
	bhis	80$
	movb	#' ,(r1)+
	br	70$
80$:	mov	f.nrbd(r5),r1	;Get actual length to write
	add	#LBSIZE,r1
	put$s	#$stdou,#lbuf,r1;Write it out
	br	20$
 
90$:	mov	(sp)+,r5	;Restore registers
	mov	(sp)+,r2
	mov	(sp)+,r1
	mov	(sp)+,r0
	return			;To caller
 
;+
; ** filnam -- put out a file name message
;
; Inputs:
;	r5=fdb pointer
;-
 
filnam:	mov	r0,-(sp)	;Save registers
	mov	r1,-(sp)
	mov	r5,r0		;Convert name
	mov	#fmsgf,r1
	call	$fdbta
	sub	#fmsg,r1	;Compute record size
	put$s	#$stdou,#fmsg,r1;Write it out
	mov	(sp)+,r1	;Restore, and
	mov	(sp)+,r0
	return			;Return
 
;+
; ** compil -- compile a text pattern
;
; Inputs:
;	r0=pointer to asciz pattern
;
; Outputs:
;	pbuf=compiled pattern
;	c bit set on error; c bit clear if all ok
;-
 
compil:	mov	r0,-(sp)	;Save registers
	mov	r1,-(sp)
	mov	r2,-(sp)
	mov	r3,-(sp)
 
	mov	#pbuf,r1	;Working pattern pointer
10$:	movb	(r0)+,r2	;Get character from pattern
	beq	110$		;End of pattern
 
	cmpb	r2,#'*		;Closure
	bne	40$		;Br if not
	cmp	r1,#pbuf	;If nothing to close then
	beq	120$		;Error
	cmpb	(r1),#BOL	;^* is illegal
	beq	120$
	cmpb	(r1),#EOL	;$* is illegal
	beq	120$
	cmpb	(r1),#CLO	;** is illegal
	beq	120$
	mov	#END,r2		;Put end on the closed pattern
	call	putpat
	call	putpat		;Allow room for the CLO
	bcs	130$		;Must fit
	mov	r1,-(sp)	;Save pointer to pattern end
20$:	dec	r1		;Slide the closed
	cmp	r1,r3		;Pattern
	blos	30$		;Along
	movb	-1(r1),(r1)	;As the CLO opcode
	br	20$		;Goes first
30$:	movb	#CLO,(r1)	;Insert closure opcode
	mov	(sp)+,r1	;Restore output pointer
	br	10$		;Next
 
40$:	mov	r1,r3		;Set pointer to last pattern
	cmpb	r2,#'.		;Any
	bne	50$		;No
	mov	#ANY,r2		;Yes, get opcode
	br	100$		;Store
 
50$:	cmpb	r2,#'^		;Beginning of line
	bne	60$		;No
	mov	#BOL,r2		;Yes, get opcode
	br	100$		;Store
 
60$:	cmpb	r2,#'$		;End of line
	bne	70$		;No
	mov	#EOL,r2		;Yes, get opcode
	br	100$		;Store
 
70$:	cmpb	r2,#'[		;Character class
	bne	80$		;No
	call	getcc		;Yes, get character class
	bcs	130$		;Bad class
	br	10$		;Go for more
 
80$:	cmpb	r2,#'\		;Escape
	bne	90$		;No
	tstb	(r0)		;Yes, test for trailing escape
	beq	90$		;Treat as literal backslash
	movb	(r0)+,r2	;Get pattern character
90$:	mov	r2,-(sp)	;Save character
	mov	#CHAR,r2	;Put out opcode
	call	putpat		;For character
	mov	(sp)+,r2	;Get character back
	call	maplow		;Make uniform case (caseless match)
100$:	call	putpat		;Save in pattern and
	br	10$		;Go for more
 
110$:	mov	#END,r2		;Finish off pattern
	call	putpat
	br	130$
120$:	sec			;Errors enter here
130$:	mov	(sp)+,r3	;Restore registers
	mov	(sp)+,r2
	mov	(sp)+,r1
	mov	(sp)+,r0
	return			;Return to caller
 
;+
; ** getcc -- compile a character class specifier.
;
; Inputs:
;	r0=pointer to first character in the class block
;	r1=pointer into output buffer for pattern
;
; Outputs:
;	r0=advanced
;	r1=advanced
;	r2=junk
;-
 
getcc:	mov	r3,-(sp)	;Save from our nasty hands
	mov	#CCL,r2		;Opcode
	cmpb	(r0),#'\	;Escape at the start
	beq	10$		;Yes, cannot be negated class
	cmpb	(r0),#'^	;Negated class
	bne	10$		;No
	inc	r0		;Skip over the negation
	mov	#NCCL,r2	;New opcode
10$:	call	putpat		;Save in the pattern
 
	mov	r1,r3		;Address of count
	clr	r2		;Put out byte count
	call	putpat		;Initially as zero
	bcs	60$		;Must fit
 
20$:	movb	(r0)+,r2	;Character from the pattern
	beq	40$		;Oops
	cmpb	r2,#']		;End of pattern?
	beq	50$		;Yes
	cmpb	r2,#'\		;Escape
	bne	30$		;No
	movb	(r0)+,r2	;Get actual character
	beq	40$		;Oops
30$:	call	putpat		;Store character in pattern
	incb	(r3)		;Fixup character count
	bcc	20$		;Br if <255 characters
40$:	sec			;Error return
	br	60$
50$:	clc			;Good return
60$:	mov	(sp)+,r3	;Restore r3
	return			;Done
 
;+
; ** putpat -- store a byte into the pattern (if it fits)
;
; Inputs:
;	r1=pointer into pbuf
;	r2=the byte to store
;
; Outputs:
;	r1=updated
;	c bit set if no fit; clear if all ok
;-
 
putpat:	cmp	r1,#pbuf+PBSIZE	;Does it fit
	bhis	10$		;No
	movb	r2,(r1)+	;Yes, store it
	clc			;Return with carry clear
	return
10$:	sec			;No fit
	return
 
;+
; ** maplow -- map character in r2 to lower case
;-
 
maplow:	cmpb	r2,#'A		;Range check
	blo	10$		;Too low
	cmpb	r2,#'Z
	bhi	10$		;Too high
	add	#'a-'A,r2	;Make lower
10$:	return			;Done
 
;+
; ** amatch -- look for a match anywhere in the line
;
; Inputs:
;	pbuf=pattern
;	rbuf=text line (asciz)
;
; Outputs:
;	r0=junk
;	r1=junk
;	r2=junk
;	c bit clear if a match; set otherwise
;-
 
amatch:	mov	#pbuf,r1	;Get pattern
	mov	#rbuf,r2	;Text pointer
10$:	tstb	(r2)		;End of the line?
	beq	20$		;Br if yes
	mov	r2,r0		;Try for a
	call	match		;Match
	bcc	30$		;Yes
	inc	r2		;No, advance pointer and
	br	10$		;Try again
20$:	sec			;C bit set if no match
30$:	return			;Done
 
;+
; ** match -- pattern matcher
;
; Inputs:
;	r0=pointer to asciz text string
;	r1=pointer to pattern
;
; Outputs:
;	r0=pointer past the matched text (success only)
;	c bit clear if match; set otherwise
;-
 
match:	mov	r1,-(sp)	;Save registers
	mov	r2,-(sp)
	mov	r3,-(sp)
 
10$:	movb	(r1)+,r2	;Pattern opcode
	jmp	@20$(r2)	;Go to executor
 
20$:	.word	150$		;END
	.word	30$		;CHAR
	.word	40$		;BOL
	.word	50$		;EOL
	.word	60$		;ANY
	.word	70$		;CCL
	.word	80$		;NCCL
	.word	90$		;CLO
 
30$:	movb	(r0)+,r2	;Char from the text
	call	maplow		;Insure case insensitivity
	cmpb	r2,(r1)+	;Check for match
	beq	10$		;Yes
	br	160$		;No
 
40$:	cmp	r0,#rbuf	;At BOL
	beq	10$		;Br if yes
	br	160$		;No
 
50$:	tstb	(r0)		;At EOL
	beq	10$		;Yes
	br	160$		;No
 
60$:	tstb	(r0)+		;Trying to match EOL
	bne	10$		;No, ok
	br	160$		;EOL fails
 
70$:	call	cmatch		;Match character class
	bcc	10$		;If match, ok
	br	160$		;Otherwise fail
 
80$:	call	cmatch		;Match character class
	bcs	10$		;If no match, ok
	br	160$		;Otherwise, fail
 
90$:	mov	r0,-(sp)	;Save where we are
	mov	r0,-(sp)	;And how far I got
100$:	call	match		;Match as many times as you can
	bcs	110$
	mov	r0,(sp)		;Remembering how far you got
	br	100$
110$:	mov	(sp)+,r0	;Get pointer to character that failed
120$:	cmpb	(r1)+,#END	;No nested closures will work
	bne	120$
130$:	mov	r0,-(sp)	;Save pointer
	call	match		;Try to match the rest
	bcc	140$		;Yes, entire pattern matches
	mov	(sp)+,r0	;Backup 1 byte
	dec	r0
	cmp	r0,(sp)		;Until null match
	bhis	130$
	tst	(sp)+		;Cleanup
	br	160$		;Report failure
140$:	cmp	(sp)+,(sp)+	;Cleanup
150$:	clc			;Success
	br	170$
160$:	sec			;Failure
170$:	mov	(sp)+,r3	;Restore registers
	mov	(sp)+,r2
	mov	(sp)+,r1
	return			;To caller
 
;+
; ** cmatch -- match a character class
;
; Inputs:
;	r0=pointer to text
;	r1=pointer to count of class
;
; Outputs:
;	r0=pointer to next character
;	r1=pointer past the pattern
;	c bit set if no match; clear if ok match
;-
 
cmatch:	movb	(r0)+,r2	;Get text byte
	call	maplow
	clr	r3		;Get number of characters in class
	bisb	(r1)+,r3
	beq	30$		;Null, success
10$:	cmpb	r2,(r1)+	;Match class character
	beq	20$		;Yes
	sob	r3,10$		;Try them all
	sec			;Failure
	br	40$
20$:	add	r3,r1		;Fixup R1
	dec	r1
30$:	clc			;Success
40$:	return			;To caller
 
	.end
