//
// File search pattern compiler
// last edited September 18, 1980  5:31 PM
//
// Copyright Xerox Corporation 1979, 1980

	get "findpkgdefs.d"
	get "findintdefs.d"

external	// entry point
[	FindCompile	// (pat, chartab[, wildchar, fuzz, outs, storeproc, rregs, lvTables, zone]) -> error/0
]


external
[		// O.S.
	Allocate
	DefaultArgs; Dvec
	MoveBlock; MyFrame
	Puts
	ReturnFrom
	SetBlock
	sysZone
	Usc
	Wss
	Zero
		// rwreg
	WriteInsReg
	WriteReg
		// Template
	PutTemplate
		// FindCompMu
	fRamImage
		// FindNext
	findJumpRam
	findWriteReg
]


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

structure MIF:	// Microinstruction field value
[	w0 word	// add this to word 0
	w1 word	// add this to word 1
	oproc word	// output procedure (or 0=Wss)
	name word 0	// name starts here
]


manifest [
	ramsize = 2000b
	consize = 400b
	charsetsize = 200b
	regsize = 100b
	maxpatsize = regsize
	fixedram = 40b	// space occupied by common microcode
	START = 20b	// entry to Nova emulator
	workarea = 10b	// 2-instruction work area for RWREG
]


static [	// other statics
	FCoutStream	// trace stream for symbolic output, or 0
	FCstoreProc	// if true, store instructions in RAM
	topframe	// return frame in case of error
	allocbits	// bit table for allocating RAM
	reglist	// list of R registers
	constloc	// constant locations
	constval	// constant values
]


let error(s) be ReturnFrom(topframe, s)

let toolong() be error("Pattern too long")

let FindCompile(pat, chartab, wildchar, fuzz, outs, storeproc, rregs, lvTables, zone; numargs na) = valof
[	let dummy = nil	// default for lvTables
	let sproc = storeproc	// To avoid defaulting explicitly supplied value of false
	DefaultArgs(lv na, -2, -1, 0, 0, true, table[ 1; 0; 77777b; 177776b ], lv dummy, sysZone)
	if na ge 6 then storeproc = sproc
	FCoutStream, FCstoreProc = outs, storeproc
	@lvTables = 0

// Validate and unpack pattern
	for c = 0 to charsetsize-1 do
	 if (chartab!c ge 0) & (chartab!(chartab!c) ne classOther) then
	   error("Invalid chartab")
	let patlen = pat>>BS.length
	if patlen eq 0 then error("Null pattern not allowed")
	let nphase = patlen+(patlen&1)	// always even
	let xpat = patlen+1
	Dvec(FindCompile, lv xpat)
	xpat!0 = patlen
	SetBlock(xpat+1, -1, patlen)
	let count = vec charsetsize
	Zero(count, charsetsize)
	let xchar = charsetsize-1
	for i = 1 to patlen do	// allocate B dispatch
	 [ let c = pat>>BS.char↑i
	   if c eq wildchar then
	    [ fuzz = fuzz+1
	      loop
	    ]
	   if chartab!c ge 0 then c = chartab!c
	   if chartab!c eq classExit then xchar = c
	   xpat!i = c
	   count!c = count!c+1
	 ]
	if fuzz ge patlen then error("Too fuzzy")

// Set up values for FindNext
	fNegK = -patlen+1+fuzz
	fNphase = nphase
	fExitChar = (xchar lshift 8) + xchar

// First try compiling to microcode, then to assembly code
	let err = FindCompileRam(pat, chartab, nphase, xpat, count, rregs)
	if err eq 0 resultis 0
	resultis FindCompileSoft(pat, chartab, nphase, xpat, count, lvTables, zone)
]

//
// The version of the compiler that produces microcode
//


and FindCompileRam(pat, chartab, nphase, xpat, count, rregs) = valof
[	topframe = MyFrame()

// Miscellaneous subroutines


// Initialize microinstruction field values
	manifest q = 400b
	let aluPLUS1 = table[ 5*200b; 0; 0; 2*q+$+; $1*q ]
	let aluMINUS1 = table[ 6*200b; 0; 0; 2*q+$-; $1*q ]
	let bsLOADR = table[ 1*20b; 0; 0; 2*q+$←; $L*q ]
	let f1TASK = table[ 2*1b; 0; -2; 4*q+$T; $A*q+$S; $K*q ]
	let f1const = table[ 7*1b; -7*10000b; -1 ]
	let f2BUSEQ0 = table[ 0; 1*10000b; -2; 5*q+$B; $U*q+$S; $=*q+$0 ]
	let f2BUS = table[ 0; 4*10000b; -2; 3*q+$B; $U*q+$S ]
	let f2IDISP = table[ 0; 15b*10000b; -2; 5*q+$I; $D*q+$I; $S*q+$P ]
	let LOADL = table[ 0; -1*2000b; -2; 2*q+$L; $←*q ]
	let rAC0 = table[ 3 lshift 11; 0; 0; 3*q+$A; $C*q+$0 ]
	let rPhase = table[ Rphase lshift 11; 0; 0; 5*q+$p; $h*q+$a; $s*q+$e ]
	let rNegK1 = table[ RnegK1 lshift 11; 0; 0; 4*q+$-; $K*q+$+; $1*q ]

// Initialize RAM
	let RamImage = fRamImage(workarea, (FCstoreProc eq true? fixedram, -1), error)	// Get code from FindCompMu


// Routine to produce microinstructions

let comment(s) be
if FCoutStream ne 0 then PutTemplate(FCoutStream, "; ******************** $S*N", s)

let ctrsel(n) = valof
[	static [ ctrw0; ctrw1 = 0; ctrproc; ctrnum ]
	let wctrn(st, p) be PutTemplate(st, "s$O", @p)
	let reg = reglist!n
	ctrw0 = reg lshift 11 + (reg ge 40b? 3*20b, 0*20b)
	ctrproc, ctrnum = wctrn, n
	resultis lv ctrw0
]

let consel(n) = valof
[	static [ conw0; conw1 = 7*10000b; conproc; connum ]
	let wconn(st, p) be PutTemplate(st, "=$O", constval!(@p))
	let loc = constloc!n
	conw0 = (loc&370b) lshift 8 + (loc&7) lshift 4
	conproc, connum = wconn, n
	resultis lv conw0
]

let instr(loc, nil, nil, nil, nil, nil, nil, nil, nil, nil; numargs na) = valof
[	let w0, w1 = 10b, 102000b	// no-op
	for j = 1 to na-2 do
	 [ let f = (lv loc)!j
	   w0 = w0 + f>>MIF.w0
	   w1 = w1 + f>>MIF.w1
	 ]
	let next = (lv loc)!(na-1)
	w1 = w1+next
	if FCoutStream ne 0 then
	 [ PutTemplate(FCoutStream, "$4O: $6UO $6UO | $4O:   ", loc, w0, w1, loc)
	   for j = 1 to na-2 do
	    [ let f = (lv loc)!j
	      let proc = f>>MIF.oproc
	      if proc eq -1 loop
	      test proc eq -2
	      ifso
	      [ if j gr 1 then Wss(FCoutStream, ", ")
	        Wss(FCoutStream, lv f>>MIF.name)
	      ]
	      ifnot
	        (proc eq 0? Wss, proc)(FCoutStream, lv f>>MIF.name)
	    ]
	   PutTemplate(FCoutStream, (na eq 2? ":$O;*N", ", :$O;*N"), next)
	 ]
	if FCstoreProc ne 0 then (FCstoreProc eq true? WriteInsReg, FCstoreProc)(loc, lv w0)
	resultis next
]


// Bit table routines

let getbit(tab, i) =
	tab!(i rshift 4) & (100000b rshift (i&17b))

let setbit(tab, i) be	// invert bit
[	let p = tab + i rshift 4
	@p = @p xor (100000b rshift (i&17b))
]

let allocins() = valof
[	let j = ramsize/16-1
	while allocbits!j eq 0 do
	 [ if j eq 0 then error("RAM full")
	   j = j-1
	 ]
	let i = j*16+15
	while getbit(allocbits, i) eq 0 do i = i-1
	setbit(allocbits, i)
	resultis i
]

let prealloc(loc, delta, n, trying; numargs na) = valof
[	if na ls 4 then trying = false
	for m = 0 to n-1 do
	 [ let lc = loc+m*delta
	   if getbit(allocbits, lc) eq 0 resultis -1
	   unless trying do setbit(allocbits, lc)
	 ]
	resultis loc
]

let findalloc(loc, delta, n, inc) = valof
[	let lc, lim = loc, ramsize-delta*n
	while lc le lim do
	 [ if prealloc(lc, delta, n, true) ne -1 then
	    [ prealloc(lc, delta, n)
	      resultis lc
	    ]
	   lc = lc+inc
	 ]
	error("Can't preallocate")
]


// Main routines

let nextmask(n) = valof
[	let m = 1
	while m le n do m = m lshift 1
	resultis m-1
]

// Unpack R register list
	let regv = vec regsize
	let patmax = 0
	for i = 0 to regsize-1 do
	 if getbit(rregs, i) ne 0 then
	 [ regv!patmax = i
	   patmax = patmax+1
	 ]
	reglist = regv
	patmax = patmax & -2

// Unpack pattern
	comment("Pattern is:")
	comment(pat)
	if nphase gr patmax then toolong()
	let npmask = nextmask((nphase-1)*2)+1
	let patlen = xpat!0

// Scan constant memory
	manifest [ nxcon = 3 ]
	manifest [ nconst = maxpatsize+nxcon ]
	let cloc, cval = vec nconst, vec nconst
	cloc, cval = cloc+nxcon, cval+nxcon
	for j = -nxcon to nphase-1 do
	 [ let val = selecton j into
	    [ case -3: 1777b	// for resetting phase
	      case -2: 6000b	// ditto
	      case -1: -1	// for signalling match
	      default: j
	    ]
	   for i = 0 to consize-1 do
	    if RamImage!i eq val then
	    [ cloc!j, cval!j = i, val; goto found ]
	   error("Inadequate constant memory")
found:
	 ]
	constloc, constval = cloc, cval

// Allocate dispatches
	let albits = vec (ramsize/16)
	allocbits = albits
	SetBlock(allocbits, -1, ramsize/16)
	let abase = ramsize-charsetsize
	prealloc(abase, 1, charsetsize)	// allocate A dispatch
	let loc = (npmask ls fixedram? fixedram, npmask)
	prealloc(0, 1, loc)	// skip bottom loc.s
	let endloc = vec (maxpatsize)
	let idloc = count
	for c = 0 to charsetsize-1 do	// allocate B dispatch
	 test idloc!c ne 0	// occurs in pattern
	 ifso
	  [ loc = findalloc(loc, 1, nphase, npmask)
	    idloc!c = loc
	  ]
	 ifnot idloc!c = -1
	let clrbase = findalloc(0, 1, nphase, npmask)
	let clrnext = allocins()
	comment("Last test")
	let next = 2
	for ctr = 0 to nphase-1 do
	 [ endloc!ctr = allocins()
	   next = findalloc(next, 1, 2, 2)
	   instr(endloc!ctr, ctrsel(ctr), bsLOADR, next)
	   instr(next, rPhase, f2BUS, clrnext)
	   instr(next+1, LOADL, consel(ctr), Match)
	 ]

// Compile test sequences
[	let tlocs = vec maxpatsize
	for c = 0 to charsetsize-1 do
	 if idloc!c ne -1 then
	 [ let cs = "Code for ' '"
	   cs>>BS.char↑11 = c
	   comment(cs)
	   let nl = -1
	   for i = 1 to patlen do
	    if xpat!i eq c then
	    [ nl = nl+1; tlocs!nl = i ]
	   for h = 0 to nphase-1 do
	    [ if nl ne 0 then comment("New phase")
	      let loc = idloc!c+h
	      for j = 0 to nl do
	       [ let ctr = (h-tlocs!j+1+nphase) rem nphase
	         let n1 = (j eq nl? endloc!ctr, allocins())
	         instr(loc, LOADL, ctrsel(ctr), aluPLUS1, f2BUSEQ0, n1)	// *** should have TASK
	         if j ne nl then
	          [ next = findalloc(next, 1, 2, 2)
	            instr(n1, ctrsel(ctr), bsLOADR, next)
	            instr(next+1, LOADL, consel(ctr), Match)
	            loc = next
	          ]
	       ]
	    ]
	 ]
]

// Store C (counter-clearing) code
[	comment("Clearing counters")
	instr(clrnext, LOADL, rNegK1, f2IDISP, clrbase)
	for h = 0 to nphase-1 do
	 [ let loc, fetch = clrbase+h, ((h&1) eq 0? FetchOdd, FetchEven)
	   let nh = (h+1) rem nphase
	   test nh eq 0
	   ifso	// also reset phase
	    [ next = findalloc(next, 1, 2, 2)
	      instr(loc, ctrsel(nh), bsLOADR, next)
	      let n1 = allocins()
	      instr(next, LOADL, consel(-3), f1const, f2IDISP, n1)
	      instr(next+1, LOADL, consel(-2), f1const, aluMINUS1, f2IDISP, n1)
	      instr(n1, rPhase, bsLOADR, fetch)
	    ]
	   ifnot
	    [ instr(loc, ctrsel(nh), bsLOADR, fetch)
	    ]
	 ]
]

// Compile reset sequence
[	comment ("Reset all counters")
	let n1 = allocins()
	instr(Reset, LOADL, rNegK1, n1)
	for i = 0 to nphase-1 do
	 [ let n2 = allocins()
	   instr(n1, ctrsel(i), bsLOADR, n2)
	   n1 = n2
	 ]
	let n2 = allocins()
	instr(n1, LOADL, rAC0, f1TASK, n2)
	instr(n2, rPhase, bsLOADR, START)
]

// Fill in character (A) dispatch
	let xchar = charsetsize-1
[	comment("A dispatch")
	let outt = FCoutStream
	for c = 0 to charsetsize-1 do
	 if (chartab!c eq classOther) & (idloc!c ne -1) then
	[ let l1 = allocins()
	  instr(l1, idloc!c)
	  idloc!c = l1
	]
	for c = 0 to charsetsize-1 do
	 [ FCoutStream = outt
	   let c1 = chartab!c
	   switchon c1 into
	   [ case classSkip:
	      instr(abase+c, Skip)
	      endcase
	     case classRecord:
	      instr(abase+c, LOADL, rPhase, Record)
	      endcase
	     case classExit:
	      instr(abase+c, LOADL, consel(-1), Match)
	      endcase
	     case classOther:
	      c1 = c	// falls through
	     default:
	      switchon idloc!c1 into
	      [ case -1:
	         FCoutStream = 0
	         instr(abase+c, rPhase, f2BUS, clrnext)
	         endcase
	        default:
	         instr(abase+c, rPhase, f2BUS, idloc!c1)	// *** should have TASK
	      ]
	   ]
	 ]
	FCoutStream = outt
]

// Set up values for FindNext
	findJumpRam = 0	// FindInit sets it up
	findWriteReg = WriteReg

	resultis 0
]

//
// An alternative compiler that doesn't use microcode
//


and FindCompileSoft(pat, chartab, nphase, xpat, count, lvTables, zone) = valof
[
external
[		// fpsoftasm
	@flvlvlvCtrs; @fnNctrs; @flvDisp
	fJumpRam; fWriteReg
	fClear; fSkip; fExit; @fCount
]
	topframe = MyFrame()

// Miscellaneous subroutines

let outblk(addr, len) be
[	for i = 0 to len-1 do
	[ if (i rem 6) eq 0 then PutTemplate(FCoutStream, "*N$6UO", addr+i)
	  PutTemplate(FCoutStream, "$8UO", addr!i)
	]
	Puts(FCoutStream, $*N)
]

// Allocate state vector
	let nchars, nlit = 0, 0
	for i = 0 to charsetsize-1 do
	  if count!i ne 0 then nchars, nlit = nchars+1, nlit+count!i
	if nphase gr 376b then toolong()	// >64K of tables needed!
	let state = Allocate(zone,
	  nphase+	// counters
	  charsetsize+	// A dispatch
	  2*nchars+nlit+	// B dispatches
	  nphase+2+	// First level of counter addresses
	  nphase*(nphase+1),	// Second level
	 -1)
	if state eq 0 then toolong()	// Can't allocate tables
	@lvTables = state
	let ctrbase = state
	let adisp = ctrbase+nphase
	  SetBlock(adisp, fClear, charsetsize)
	let bdisp = adisp+charsetsize
	let ppcbase = bdisp+2*nchars+nlit+1
	let pcbase = ppcbase+nphase+1

// Produce B dispatch tables
[	let bptr = bdisp
	for c = 0 to charsetsize-1 do
	 if count!c ne 0 then
	[ adisp!c = bptr
	  @bptr = 6000b+lv fCount	// jsr @
	  bptr = bptr+1
	  for i = 1 to xpat!0 do
	   if xpat!i eq c then
	  [ @bptr = i-1; bptr = bptr+1 ]
	  @bptr = -1
	  bptr = bptr+1
	]
]

// Fill in character (A) dispatch
[	for c = 0 to charsetsize-1 do
	 [ let c1 = chartab!c
	   let d = nil
	   switchon c1 into
	   [ case classSkip:
	      adisp!c = fSkip
	      endcase
	     case classRecord:
	      adisp!c = fExit
	      endcase
	     case classExit:
	      adisp!c = fExit
	      endcase
	     case classOther:
	      endcase	// already done
	     default:
	      if count!c1 eq 0 loop
	      adisp!c = adisp!c1
	   ]
	 ]
]

// Set up pointer tables
[	ppcbase!-1 = -1
	let p = pcbase
	for i = 0 to nphase-1 do
	[ p = p+1
	  ppcbase!i = p
	  for j = 0 to nphase-1 do
	    p!j = ctrbase+((i-j+nphase) rem nphase)
	  p!-1 = p!(nphase-1)
	  p = p+nphase
	]
	ppcbase!nphase = 0
]

// Write listing
if FCoutStream ne 0 then
[	outblk(adisp, charsetsize)
	outblk(bdisp, ppcbase-bdisp)
	outblk(ppcbase-1, nphase+2)
	outblk(pcbase, nphase*(nphase+1))
]

// Set up values for FindNext
	findJumpRam = fJumpRam
	findWriteReg = fWriteReg

// Set up data for assembly code
	flvDisp = adisp
	fnNctrs = -nphase
	flvlvlvCtrs = ppcbase

	resultis 0
]