-- DBStashImpl.Mesa
-- last edit by Schmidt, January 6, 1983 1:44 pm
-- last edit by Satterthwaite, February 1, 1983 10:08 am

-- This module provides a cache of dependencies (DepSeq's) for the modeller
-- it is designed to operate independently so that the modeller can be changed.
-- without needing to replace this module, so it is loaded separately only
-- once by the Modeller.  In particular it is bound in its own config
-- (DBStashPack.Config) and does not rely on any Modeller procedures.
-- The cache is indexed by file create time.  There is only one DepSeq per
-- create time.
-- Things get tricky when these DepSeq's are passed to the Modeller, so
-- we adopt the convention that the Modeller never FREE's anything in the DepSeq, 
-- including the strings.  If necessary, strings may be put into the DepSeq
-- using the depseq.CopyString[] call.  This allows the memory for the Modeller
-- and this module to be COMPLETELY separate.  In fact the strings in the 
-- depseq's stored in no heap at all.


DIRECTORY
  CWF: TYPE USING [FWF0, FWF1, FWF2, FWF3, FWF4, 
  	SetWriteProcedure, SWF1, WF0, WF1, WF3, WFCR],
  DBStash: TYPE USING [],
  Dir: TYPE USING [DepSeq, DepSeqRecord, MaximumDepSize],
  Inline: TYPE USING [LongCOPY],
  IO: TYPE USING[Handle, PutChar],
  LongString: TYPE USING [AppendString, EquivalentString],
  Rope: TYPE USING[Text],
  RopeInline: TYPE USING[InlineFlatten],
  Subr: TYPE USING [AbortMyself, Any,   
  	HugeZone, numberofleaders, PagesUsedInHugeZone, 
		strcpy, SubrInit, SubrStop],
  TimeStamp: TYPE USING [Null, Stamp],
  UECP: TYPE USING[Argv, Parse],
  UserExec: TYPE USING[CommandProc, GetStreams, RegisterCommand, UserAbort];

DBStashImpl: MONITOR
IMPORTS CWF, Inline, IO, LongString, RopeInline, Subr, UECP, UserExec
EXPORTS DBStash = {

STASHSIZE: CARDINAL = 500;

StashSeq: TYPE = LONG POINTER TO StashSeqRecord;
StashSeqRecord: TYPE = RECORD[
	stashzone: UNCOUNTED ZONE ← NIL,
	temporarydepseq: Dir.DepSeq ← NIL,
	nhits: LONG CARDINAL ← 0,
	nmisses: LONG CARDINAL ← 0,
	size: CARDINAL ← 0,
	body: SEQUENCE maxsize: CARDINAL OF StashRecord
	];
	
ST: TYPE = LONG POINTER TO StashRecord;
StashRecord: TYPE = RECORD[
	create: LONG CARDINAL ← 0,
	fileName: LONG STRING ← NIL,	-- fileName and create are linked
	bcdVers: TimeStamp.Stamp ← TimeStamp.Null,
	depseq: Dir.DepSeq ← NIL
	];
	
-- MDS usage!!!
stashseq: StashSeq ← NIL;
-- endof mds usage


-- uses linear lookup, should use a hash of some sort
Lookup: PUBLIC ENTRY PROC[create: LONG CARDINAL, fileName: LONG STRING] 
	RETURNS[depseq: Dir.DepSeq] = {
ENABLE UNWIND => NULL;
RETURN[InternalLookup[create, fileName]];
};

InternalLookup: INTERNAL PROC[create: LONG CARDINAL, fileName: LONG STRING] 
	RETURNS[depseq: Dir.DepSeq] = {
st: ST;
InternalInit[];
IF create = 0 THEN ERROR;
FOR i: CARDINAL IN [0 .. stashseq.size) DO
	st ← @stashseq[i];
	IF create = st.create 
	AND LongString.EquivalentString[fileName, st.fileName] THEN {
		stashseq.nhits ← stashseq.nhits + 1;
		RETURN[st.depseq];
		};
	ENDLOOP;
stashseq.nmisses ← stashseq.nmisses + 1;
RETURN[NIL];
};

BcdVersLookup: PUBLIC ENTRY PROC[bcdVers: TimeStamp.Stamp] RETURNS[depseq: Dir.DepSeq] = {
ENABLE UNWIND => NULL;
st: ST;
InternalInit[];
IF bcdVers = TimeStamp.Null THEN ERROR;
FOR i: CARDINAL IN [0 .. stashseq.size) DO
	st ← @stashseq[i];
	IF bcdVers = st.bcdVers THEN {
		stashseq.nhits ← stashseq.nhits + 1;
		RETURN[st.depseq];
		};
	ENDLOOP;
stashseq.nmisses ← stashseq.nmisses + 1;
RETURN[NIL];
};

Insert: PUBLIC ENTRY PROC[create: LONG CARDINAL, fileName: LONG STRING, depseq: Dir.DepSeq] 
	RETURNS[newdepseq: Dir.DepSeq] = {
ENABLE UNWIND => NULL;
RETURN[InternalInsert[create, fileName, depseq]];
};

-- it is an error to Insert something that is already in the cache
InternalInsert: INTERNAL PROC[create: LONG CARDINAL, fileName: LONG STRING, depseq: Dir.DepSeq] 
	RETURNS[newdepseq: Dir.DepSeq] = {
newdepseq ← depseq;
InternalInit[];
FOR i: CARDINAL IN [0 .. stashseq.size) DO
	IF create = stashseq[i].create 
	AND LongString.EquivalentString[fileName, stashseq[i].fileName] THEN ERROR;
	ENDLOOP;
IF stashseq.size >= stashseq.maxsize THEN {
	CWF.WF0["Error - have run out of stash slots.\n"L];
	RETURN;
	};
stashseq[stashseq.size].create ← create;
stashseq[stashseq.size].fileName ← SpecialCopyString[fileName];
stashseq[stashseq.size].bcdVers ← IF depseq.fromsource THEN TimeStamp.Null 
				  ELSE depseq.bcdVers;	-- may be Null
IF depseq ~= stashseq.temporarydepseq THEN ERROR;
-- this allows us to insert only pieces that are really being used
newdepseq ← AllocateDepSeq[depseq.size];
Inline.LongCOPY[to: newdepseq, from: depseq, 
	nwords: SIZE[Dir.DepSeqRecord[depseq.size]]];
depseq.bcdVers ← TimeStamp.Null;
depseq.size ← 0;
depseq.bcdFileName ← depseq.srcFileName ← depseq.moduleName ← NIL;
stashseq[stashseq.size].depseq ← newdepseq;
stashseq.size ← stashseq.size + 1;
};

GetTemporaryDepSeq: PUBLIC ENTRY PROC RETURNS[Dir.DepSeq] = {
ENABLE UNWIND => NULL;
RETURN[stashseq.temporarydepseq];
};

-- in what follows, I use a different Heap (stashseq.stashzone)
-- for the DepSeqRecord and the Strings in it.  
-- A homebuilt Heap is used for the DepSeqRecords, StashSeq's, and strings in it.

SpecialCopyString: PROC[old: LONG STRING, zone: UNCOUNTED ZONE ← NIL] 
	RETURNS[new: LONG STRING] = {
hugezone: UNCOUNTED ZONE ← stashseq.stashzone;
IF old = NIL THEN ERROR;
new ← hugezone.NEW[StringBody[old.length]
	← StringBody[length: 0, maxlength: old.length, text:] ];
Subr.strcpy[new, old];
};

InternalInit: INTERNAL PROC = {
stashzone: UNCOUNTED ZONE;
IF stashseq ~= NIL THEN RETURN;
stashzone ← Subr.HugeZone[];
stashseq ← stashzone.NEW[StashSeqRecord[STASHSIZE]];
stashseq.stashzone ← stashzone;
stashseq.temporarydepseq ← AllocateDepSeq[Dir.MaximumDepSize];
};

-- utility procedures
AllocateDepSeq: PROC[size: CARDINAL] RETURNS[depseq: Dir.DepSeq] = {
depseq ← stashseq.stashzone.NEW[Dir.DepSeqRecord[size]];
-- this so we can be sure we have the right strings
depseq.CopyString ← SpecialCopyString;
};

SetNLeaders: PUBLIC ENTRY PROC = {
InternalInit[];
Subr.numberofleaders ← 0;
};

GetNLeaders: PUBLIC PROC RETURNS[nleaders: CARDINAL] = {
RETURN[Subr.numberofleaders];
};

SetNHits: PUBLIC ENTRY PROC = {
InternalInit[];
stashseq.nhits ← stashseq.nmisses ← 0;
};

GetNHits: PUBLIC PROC RETURNS[nhits, nmisses: LONG CARDINAL]= {
RETURN[stashseq.nhits, stashseq.nmisses];
};

PrintSeparatorLine: PROC[wfproc: PROC[CHAR]] = {
CWF.FWF0[wfproc, "-----------------\n"L];
};

ForceOut: PUBLIC ENTRY PROC RETURNS[npages: CARDINAL] = {
IF stashseq ~= NIL THEN 
	npages ← Subr.PagesUsedInHugeZone[stashseq.stashzone]
ELSE npages ← 0;
};

-- these are the two commands available in the SimpleExec
Flush: PUBLIC ENTRY UserExec.CommandProc = TRUSTED {
ENABLE UNWIND => NULL;
MyPutChar: PROC[ch: CHAR] = {
	out.PutChar[ch];
	};
out: IO.Handle = exec.GetStreams[].out;
[] ← CWF.SetWriteProcedure[MyPutChar];
stashseq ← NIL;
Subr.SubrStop[];
CWF.WF0["Dependency Cache Flushed.\n"L];
};

Stat: UserExec.CommandProc = TRUSTED {
MyPutChar: PROC[ch: CHAR] = {
	out.PutChar[ch];
	};
out: IO.Handle = exec.GetStreams[].out;
[] ← CWF.SetWriteProcedure[MyPutChar];
CWF.WF1["Cache has %u entries:\n"L, @stashseq.size];
FOR i: CARDINAL IN [0 .. stashseq.size) DO
	s: LONG STRING;
	depseq: Dir.DepSeq;
	depseq ← stashseq[i].depseq;
	s ← IF depseq.bcdFileName ~= NIL THEN depseq.bcdFileName
	    ELSE IF depseq.srcFileName ~= NIL THEN depseq.srcFileName
	    ELSE depseq.moduleName;
	CWF.WF1["%s "L, s];
	IF UserExec.UserAbort[exec] THEN SIGNAL Subr.AbortMyself;
	ENDLOOP;
CWF.WFCR[];
};

PrintEntry: UserExec.CommandProc = TRUSTED {
out: IO.Handle = exec.GetStreams[].out;
depseq: Dir.DepSeq;
filename: STRING ← [100];
argv: UECP.Argv ← UECP.Parse[event.commandLine];
MyPutChar: PROC[ch: CHAR] = {
	out.PutChar[ch];
	};
[] ← CWF.SetWriteProcedure[MyPutChar];
FOR parm: CARDINAL IN [1 .. argv.argc) DO
	flat: Rope.Text ← RopeInline.InlineFlatten[argv[parm]];
	Subr.strcpy[filename, LOOPHOLE[flat]];
	IF NOT Subr.Any[filename, '.] THEN LongString.AppendString[filename, ".Bcd"L];
	FOR i: CARDINAL IN [0 .. stashseq.size) DO
		depseq ← stashseq[i].depseq;
		IF UserExec.UserAbort[exec] THEN SIGNAL Subr.AbortMyself;
		IF (depseq.bcdFileName ~= NIL 
		AND LongString.EquivalentString[depseq.bcdFileName, filename])
		OR (depseq.srcFileName  ~= NIL 
		AND LongString.EquivalentString[depseq.srcFileName, filename]) THEN {
			CWF.WF3["Entry under (%s, %lt, %lt):\n"L, stashseq[i].fileName, 
				@stashseq[i].create, @stashseq[i].bcdVers.time];
			PrintDepSeq[depseq, exec.GetStreams[].out];
			CWF.WFCR[];
			};
		REPEAT
		FINISHED => 
			CWF.WF1["Can't find %s in DB cache.\n"L, filename];
		ENDLOOP;
	ENDLOOP;
};

PrintDepSeq: PROC[depseq: Dir.DepSeq, window: IO.Handle] = {
zero: STRING ← "(Zero)"L;
tstr: STRING ← [30];

	WFPut: PROC[ch: CHAR] = {
	window.PutChar[ch];
	};
	
	Cvt: PROC[time: LONG CARDINAL] RETURNS[str: STRING] = {
	IF time = 0 THEN RETURN[zero]
	ELSE CWF.SWF1[tstr, "%lt"L, @time];
	RETURN[tstr];
	};
	
IF depseq.bcdFileName ~= NIL THEN
	CWF.FWF2[WFPut, "Bcd file %s of %s\n"L, depseq.bcdFileName, Cvt[depseq.bcdVers.time]];
IF depseq.srcFileName ~= NIL THEN {
	IF depseq.bcdFileName ~= NIL THEN CWF.FWF0[WFPut, "  "L];
	CWF.FWF2[WFPut, "Source file %s of %s\n"L, depseq.srcFileName, Cvt[depseq.srcCreate]];
	};
CWF.FWF1[WFPut, "ModuleName %s\n"L, depseq.moduleName];
CWF.FWF4[WFPut, "   defns %s, config %s, fromsource %s, istablecompiled %s.\n"L, 
	IF depseq.isdefns THEN "T"L ELSE "F"L,
	IF depseq.isconfig THEN "T"L ELSE "F"L,
	IF depseq.fromsource THEN "T"L ELSE "F"L,
	IF depseq.istablecompiled THEN "T"L ELSE "F"L];
FOR i: CARDINAL IN [0 .. depseq.size) DO
	CWF.FWF1[WFPut, "     %s "L, 
		SELECT depseq[i].relation FROM
		imports => "Imports:  "L,
		exports => "Exports:  "L,
		directory => "Directory:"L,
		ENDCASE => ERROR];
	CWF.FWF3[WFPut, "%s of %s (%s),\n"L, 
		depseq[i].bcdFileName, Cvt[depseq[i].bcdVers.time], depseq[i].moduleName];
	ENDLOOP;
};

OnceInit: PROC = {
Subr.SubrInit[256];
UserExec.RegisterCommand["XDBFlush.~", Flush];
UserExec.RegisterCommand["XDBPrint.~", PrintEntry];
UserExec.RegisterCommand["XDBStat.~", Stat];
};

-- the start code
OnceInit[];
}.