//
// File searching program
// last edited October 29, 1980  6:21 PM
//
// Copyright Xerox Corporation 1979, 1980

	get "findpkgdefs.d"
	get "streams.d"
	get "altofilesys.d"
	get "bcplfiles.d"

external
[		// GP
	SetupReadParam
	ReadParam
		// MDI
	LookupEntries
		// findsub
	Usc2
	occlim
	linedelim
	paradelim
	breakdelim
	nonbravo
	copyseg
	splitstream
	boldstream
	readstring
	ReadChar
		// FindCompile
	FindCompile
		// FindNext
	FindInitScan
	FindNext
		// Template
	PutTemplate
		// O.S.
	Closes; CreateDiskStream; CreateDisplayStream
	DoubleAdd; dsp
	Endofs
	FilePos; FinishScanStream; fpComCm; fpSysDir; Free
	GetLinePos; Gets
	keys
	MoveBlock
	OpenFile
	Puts
	Resets
	SetBlock; SetFilePos; ShowDisplayStream; sysZone
	Timer
	Usc
	Ws; Wss
	Zero
]


manifest
[	bufsize = 77000b	// buffer space
	mbufsize = 800	// buffer for one-line message
	displines = 45
	savematches = 50	// remember position of this many matches
	maxll = 100	// max line length
	maxnl = 20	// max paragraph length
	lvCodeTop = #335
	lvDisplayHead = #420
	dsoptions = DSstopbottom+DSstopright
	mdsoptions = 0
]


structure BS:
[	length byte
	char↑1,255 byte
]

structure MP:	// match position
[	fid word	// file number
	fa word lFA = @FA
	ppos word	// position within pattern
]
manifest lMP = size MP/16

structure FF:	// Find flags
[	itemproc word	// item delimitation procedure
	waf word	// if true, write all matches to file (allf % writef)
	allf word	// All flag
	casef word	// Case flag
	lstf word	// List flag
	multif word	// Multiple flag
	octalf word	// Octal flag
	spacef word	// Space flag
	verbatimf word	// Verbatim flag
	writef word	// Write flag
]
manifest lFF = size FF/16


static
[	charExit = 177b
	charWildCard = 1
	flags	// global flags, needed by ccproc
]

//
// Main program
//

let find(blv) be
[	let ff = vec lFF
	SetBlock(ff, false, lFF)
	let cpat = vec 30
	ff>>FF.itemproc = linedelim
	let com = OpenFile("Com.Cm", ksTypeReadOnly, charItem, 0, fpComCm)
	let nv, swv = vec 128, vec 128
	SetupReadParam(nv, swv, com, swv)
	for j = 1 to swv!0 do
	[ let ch = swv!j&137b
	  let off = getflag(ch)
	  test off ge 0
	   ifso ff!off = true
	   ifnot switchon ch into
	  [ case $B: ff>>FF.itemproc = breakdelim; endcase
	    case $L: ff>>FF.lstf = true; endcase
	    case $M: ff>>FF.multif = true; endcase
	    case $P: ff>>FF.itemproc = paradelim; endcase
	  ]
	]
	if ff>>FF.writef then ff>>FF.waf = true
	let buf = vec bufsize
	let A = blv>>BLV.overlayAddress↑0
	let B = blv>>BLV.overlayAddress↑1
	let flist = A
	addname("Find.Lst", lv flist)
	addname("Find.Matches", lv flist)
	 [ if ReadParam($P, -1, nv, swv) eq -1 break
	   addname(nv, lv flist)
	 ] repeatwhile ff>>FF.multif
	let fnames = flist
	let cfn = A
	while cfn ne fnames do
	 [ @flist = cfn
	   flist = flist+1
	   cfn = cfn + cfn>>BS.length/2+1
	 ]
	let fdvs = flist
	let nfiles = fdvs-fnames-2
	flist = fdvs+(nfiles+2)*lDV
	@lvCodeTop = flist
	let dir = CreateDiskStream(fpSysDir, ksTypeReadOnly, wordItem)
	if dir eq 0 then abor("Can't open SysDir")
	LookupEntries(dir, fnames, fdvs, nfiles+2, true, buf, bufsize)
	Closes(dir)
	let len = 0
	until Endofs(com) do
	 [ let ch = Gets(com)
	   if ch eq $*N break
	   len = len+1
	   cpat>>BS.char↑len = ch
	 ]
	cpat>>BS.length = len
	Closes(com)
	// Flush initialization code
	MoveBlock(B, A, flist-A)
	let disp = B-A
	@lvCodeTop = flist+disp
	fnames, fdvs = fnames+disp, fdvs+disp
	for i = 0 to nfiles+1 do
	   fnames!i = fnames!i+disp
	let miss = false
	for i = 2 to nfiles+1 do
	 if fdvs!(i*lDV) eq 0 then
	 [ unless miss do
	    [ Ws("Can't find the following files:")
	      miss = true
	    ]
	   Puts(dsp, $*S)
	   Ws(fnames!i)
	 ]
	if miss then abor("*N")
	let lsts, mats = 0, 0
	if ff>>FF.lstf then lsts = OpenFile(fnames!0, ksTypeWriteOnly, charItem, verLatestCreate, lv fdvs>>DV.fp)
	mats = OpenFile(fnames!1, ksTypeWriteOnly, charItem, verLatestCreate, lv (fdvs+lDV)>>DV.fp)
	flags = ff	// set up static for ccproc
	 [ let r = nil
	   if len eq 0 then
	    [ readstring("Pattern: ", dsp, cpat, ccproc)
	      if cpat>>BS.length eq 0 break
	    ]
	   ff>>FF.waf = ff>>FF.allf % ff>>FF.writef
	   r = findmain(fnames+2, fdvs+2*lDV, nfiles, cpat, ff, lsts, mats, buf, bufsize)
	   if lsts ne 0 then [ Closes(lsts); lsts = 0 ]
	   if r ne 0 then
	    [ PutTemplate(dsp, "**********$S*N", r) ]
	 ] repeatwhile len eq 0
	Closes(mats)
]

and addname(str, lvlst) be
[	let nw = str>>BS.length/2+1
	MoveBlock(@lvlst, str, nw)
	@lvlst = @lvlst+nw
]

and abor(s) be
	 [ Ws(s); finish ]

and ccproc(ds, ch) = valof
// Handle a control character during pattern input.
// The only character recognized is ↑S, which prompts for a
// switch to toggle, or ? meaning show the current state.
[	if ch ne $S-100b resultis false
	Wss(ds, " Switch: ")
	ch = ReadChar(ds, 5, 200)
	let msg = nil
	let off = getflag(ch, lv msg)
	test off ne -1
	 ifso
	[ flags!off = not flags!off
	  Wss(ds, msg)
	  Wss(ds, (flags!off? " -- on", " -- off"))
	]
	 ifnot
	test ch eq $?
	 ifso
	[ Puts(ds, $*N)
	  for i = 0 to 5 do
	  [ off = getflag(table[ $A;$C;$O;$S;$V;$W ]!i, lv msg)
	    if flags!off then PutTemplate(ds, "  $S*N", msg)
	  ]
	]
	 ifnot Wss(ds, " ???")
	resultis true
]

and getflag(ch, lvMsg; numargs na) = valof
// Convert a global flag character to an index in the FF structure,
// or -1 if not recognized.  If a second arg is supplied,
// store an explanatory message into it.
[	if na ls 2 then lvMsg = lv na
	switchon (ch&137b) into	// lower case = upper case
	[ case $A: @lvMsg = "All to file"; resultis offset FF.allf/16
	  case $C: @lvMsg = "Case matters"; resultis offset FF.casef/16
	  case $O: @lvMsg = "Octal positions"; resultis offset FF.octalf/16
	  case $S: @lvMsg = "Spaces matter"; resultis offset FF.spacef/16
	  case $V: @lvMsg = "Verbatim"; resultis offset FF.verbatimf/16
	  case $W: @lvMsg = "Write only"; resultis offset FF.writef/16
	]
	resultis -1
]

//
// Main matching code
//

and findmain(fnames, fdvs, nfiles, upat, ff, lsts, mats, buf, bsize) = valof
[	let chtab = vec 200b
	let tables = 0
	let r = compat(upat, ff, lsts, chtab, lv tables)
	if r ne 0 resultis r
	let matchpos = vec (lMP*savematches)
	let old = vec lMP
	Zero(old, lMP)
	let npages = 0
	let dh = @lvDisplayHead
	@lvDisplayHead = 0
	let btime = vec 1
	Timer(btime)
	let nmatches = getmatches(fdvs, nfiles, buf, bsize, matchpos, lv npages, old)
	let atime = vec 1
	Timer(atime)
	@lvDisplayHead = dh
	let dtime = vec 1
	dtime!0, dtime!1 = not btime!0, not btime!1
	DoubleAdd(dtime, table[ 0; 1 ])
	DoubleAdd(dtime, atime)	// dtime ← atime-btime
	PutTemplate(dsp, "  $D matches, $ED ms, $D pages", nmatches, dtime, npages)
	if nmatches gr savematches then nmatches = savematches
	let first = true
	let dbsize = (ff>>FF.writef? 0, bsize)
	if nmatches ne 0 then
	 [ let last = nmatches ls savematches
	   let nm = showmatches(fnames, fdvs, mats, buf, dbsize, matchpos, nmatches, upat>>BS.length, chtab, first, last, ff)
	   if nm ls 0 then
	   [ unless ff>>FF.waf break
	     nm = -1-nm
	     if last & (nm eq nmatches) break	// all done
	     dbsize = 0	// don't display any more
	   ]
	   MoveBlock(old, matchpos+(nm-1)*lMP, lMP)
	   nmatches = getmatches(fdvs, nfiles, buf, bsize, matchpos, 0, old)
	   first = false
	 ] repeat
	if tables ne 0 then Free(sysZone, tables)
	resultis 0
]

and compat(upat, ff, lsts, chtab, lvTables) = valof
[	// Decode user-specified pattern into string, wildcards, fuzz
	SetBlock(chtab, classOther, 200b)
	chtab!charExit = classExit
	unless ff>>FF.spacef do chtab!$*S = classSkip
	unless ff>>FF.casef do for ch = $a to $z do chtab!ch = ch+($A-$a)
	let pat = upat
	let len, fuzz = 0, 0
	let quote = false
	for i = 1 to upat>>BS.length do
	 [ let ch = upat>>BS.char↑i & 177b
	   if ch ls 40b resultis "Control char.s not allowed"
	   unless quote switchon ch into
	   [ case $~:
	      fuzz = fuzz+1
	      loop
	     case $':
	      quote = true
	      loop
	     case $*S:
	      if ff>>FF.spacef endcase
	      loop
	     case $#:
	      ch = charWildCard
	   ]
	   len = len+1
	   pat>>BS.char↑len = ch
	   quote = false
	 ]
	pat>>BS.length = len
	resultis FindCompile(pat, chtab, charWildCard, fuzz, lsts, true, 0, lvTables)
]

and getmatches(fdvs, nfiles, buf, bsize, matchpos, lvnp, old) = valof
// Returns # of matches
[	let nmatches = 0
	let fa = vec lFA
	let skip = lvnp eq 0	// skip all matches through old
	for i = old>>MP.fid to nfiles-1 do
	 [ let st = CreateDiskStream(lv (fdvs+i*lDV)>>DV.fp, charItem, ksTypeReadOnly)
	   let ssd = FindInitScan(st, buf, bsize, fa)
	    [ let ppos = FindNext()
	      if ppos ls 0 then	// end of file, ppos = not npages
	       [ if lvnp ne 0 then @lvnp = @lvnp + not ppos
	         break
	       ]
	      if skip then
	       [ if (i eq old>>MP.fid) & (ppos eq old>>MP.ppos) &
	          (fa>>FA.pageNumber eq old>>MP.pageNumber) &
	          (fa>>FA.charPos eq old>>MP.charPos) then
	            skip = false
	         loop
	       ]
	      test nmatches ls savematches
	       ifso	// save position of match
	       [ let mp = matchpos+lMP*nmatches
	         mp>>MP.fid = i
	         mp>>MP.ppos = ppos
	         mp>>MP.da = fa>>FA.da
	         mp>>MP.pageNumber = fa>>FA.pageNumber
	         mp>>MP.charPos = fa>>FA.charPos
	       ]
	       ifnot
	        if lvnp eq 0 then	// quit now
	       [ FinishScanStream(ssd)
	         Closes(st)
	         resultis nmatches
	       ]
	      nmatches = nmatches+1
	    ] repeat
	   FinishScanStream(ssd)
	   Closes(st)
	 ]
	resultis nmatches
]

//
// Output results
//

and showmatches(fnames, fdvs, mats, db, bsize, matchpos, nmatches, patlen, chtab, first, last, ff) = valof
// Returns # of matches displayed normally, -1-# if aborted or done
[	let ds, mds, ms, bolds = nil, nil, vec lST, vec lST
	test bsize eq 0
	 ifso	// just copy matches, don't display
	  ds, ms = 0, mats
	 ifnot
	[ ds = CreateDisplayStream(displines, db, bsize-mbufsize, 0, 0, dsoptions)
	  ShowDisplayStream(ds)
	  mds = CreateDisplayStream(2, db+bsize-mbufsize, mbufsize, 0, 0, mdsoptions)
	  ShowDisplayStream(mds, DSbelow, ds)
	  boldstream(bolds, ds)
	  splitstream(ms, ds, mats)
	]
	let ll, nl = nil, nil
	test ff>>FF.waf
	 ifso ll, nl = -1, -1
	 ifnot ll, nl = maxll, maxnl
	let para = ff>>FF.verbatimf & (ff>>FF.itemproc eq paradelim)
	let eop = (para? "*032*N", "*N")	// end of paragraph string
	let lastfid = (first? -1, matchpos>>MP.fid-1)
	let fs = (first? ms, ds)
	let lim = nmatches-1
	let st = 0
	let i = 0
	while i le lim do
	 [ let mp = matchpos+lMP*i
	   if mp>>MP.fid ne lastfid then
	    [ if st ne 0 then Closes(st)
	      while lastfid ne mp>>MP.fid do
	       [ lastfid = lastfid+1
	         if fs ne 0 then PutTemplate(fs, "****** $S$S", fnames!lastfid, eop)
	       ]
	      fs = ms
	      st = CreateDiskStream(lv (fdvs+lastfid*lDV)>>DV.fp, ksTypeReadOnly, charItem)
	    ]
	   let begv, mbegv, posv, mendv, endv = vec 1, vec 1, vec 1, vec 1, vec 1
	   let fa = lv mp>>MP.fa
	   let pn = fa>>FA.pageNumber-1
	   posv!0, posv!1 = pn rshift 7, pn lshift 9 + fa>>FA.charPos
	   occlim(st, posv, mbegv, mendv, mp>>MP.ppos, patlen, chtab)
	   let inl = ff>>FF.itemproc(st, mbegv, begv, endv, ll, nl)
	   unless ff>>FF.verbatimf do nonbravo(st, mbegv, begv, endv)
	   if ff>>FF.octalf then PutTemplate(mats, "$6EO$S", posv, (para? eop, " "))
	   if copyseg(st, mats, begv, endv) ne $*N then Wss(mats, eop)
	   if ds ne 0 then
	   [ PutTemplate(ds, "$6EO ", posv)
	     copyseg(st, ds, begv, mbegv)
	     let lastch = nil
	     test Usc2(endv, mendv) gr 0
	     ifso	// normal case, match falls within delimited area
	     [ copyseg(st, bolds, mbegv, mendv)
	       lastch = copyseg(st, ds, mendv, endv)
	     ]
	     ifnot	// delimited area ends within match
	       lastch = copyseg(st, bolds, mbegv, endv)
	     if lastch ne $*N then Puts(ds, $*N)
	     if (GetLinePos(ds) ge displines-5) % (i eq lim) then
	      [ test (i eq lim) & last
	         ifso
	         [ Wss(mds, "*N*T*T----- <SP> to clear screen -----*N")
	           Gets(keys)
	           i = i+1
	         ]
	         ifnot
	         [ Wss(mds, "*N*T*T----- <SP> for more, <DEL> to abort -----*N")
	           i = (Gets(keys) eq 177b? -i-2, i+1)
	         ]
	        break
	      ]
	   ]
	   i = i+1
	 ]
	if st ne 0 then Closes(st)
	if ds ne 0 then
	[ ShowDisplayStream(mds, DSdelete)
	  ShowDisplayStream(ds, DSdelete)
	]
	resultis ((i eq nmatches) & last? -i-1, i)
]