-- CTCompImpl.Mesa, last edit May 13, 1983 3:14 pm

DIRECTORY
  BcdDefs: TYPE USING [Base, FTIndex, MTIndex, NameRecord],
  BcdOps: TYPE USING [BcdBase, FTHandle, MTHandle, NameString, ProcessFiles],
  CompilerOps: TYPE USING [AppendHerald, DefaultSwitches, DoTransaction, LetterSwitches,
  	Start, Stop, StreamId, Transaction],
  ConvertUnsafe: TYPE USING[ToRope],
  CS: TYPE USING [Confirm, EndsIn, EquivalentRope, MakeTS, NewFile, NewStream,
  	ReadWrite, SetPFCodes, Write],
  CT: TYPE USING[BcdTab, BcdTabRecord, Global, MI, ModuleList],
  CTLoad: TYPE USING[ReplaceResult],
  Directory: TYPE USING [DeleteFile, Error, ignore, Lookup, Rename, UpdateDates],
  File: TYPE USING[Capability, nullCapability, read],
  FileParms: TYPE USING [ActualId, BindingProc, Name, nullActual, nullSymbolSpace,
  	SymbolSpace],
  FileStream: TYPE USING[Create, GetLeaderPropertiesForCapability],
  Heap: TYPE USING[systemZone],
  IO: TYPE USING[card, Close, CreateProcsStream, CreateRefStreamProcs,
  		Handle, PutChar, PutF, PutFR, STREAM, string, rope, UserAbort, UserAborted],
  List: TYPE USING[DRemove],
  LongString: TYPE USING [SubString, SubStringDescriptor],
  Rope: TYPE USING[Cat, Flatten, FromChar, IsEmpty, Length, ROPE, Text],
  RopeInline: TYPE USING[InlineFlatten],
  RTOS: TYPE USING[CheckForModuleReplacement],
  Space: TYPE USING [Create, CreateUniformSwapUnits, Delete, Handle, LongPointer,
  	-- MakeReadOnly, -- Map, Unmap, virtualMemory],
  Stream: TYPE USING[Handle, Delete, PutChar],
  Time: TYPE USING[Current],
  TimeStamp: TYPE USING [Null, Stamp],
  ViewerClasses: TYPE USING [Viewer], 
  ViewerOps: TYPE USING [CreateViewer, FindViewer, OpenIcon, RestoreViewer], 
  WindowManager: TYPE USING[WaitCursor, UnWaitCursor];

CTCompImpl: CEDAR MONITOR 
IMPORTS BcdOps, CompilerOps, ConvertUnsafe, CS, Directory, FileStream, Heap, IO, 
	List, Rope, RopeInline, RTOS, Space, Stream,
	Time, ViewerOps, WindowManager
EXPORTS CT = {

-- MDS usage
-- all these variables are protected by the monitor
compilerIsLocked: BOOL ← FALSE;
compilerWait: CONDITION;
logsh: IO.Handle ← NIL;		-- out stream to Compiler.Log
logpilotsh: Stream.Handle;
g: CT.Global;
sourcesh: Stream.Handle;		-- in stream to source file
good, warn, err: CARDINAL ← 0;
compilerStarted: BOOL ← FALSE;
timeCompilerStarted: LONG CARDINAL ← 0;
onePageSpace: Space.Handle;	-- this is used for getting version stamps
manyPageSpace: Space.Handle;	-- used to map in the entire Bcd
manyPageSize: CARDINAL;	-- number of pages in manyPageSpace
-- endof MDS

DetermineCompilation: PUBLIC SAFE PROC[gToUse: CT.Global, modRepl: BOOL]
 RETURNS[errors: BOOL] = TRUSTED {
	ENABLE UNWIND => ReleaseCompilerLock[];
	time: LONG CARDINAL;
	gToUse.ttyout.PutF["Compilation Phase.\n"];
	AcquireCompilerLock[gToUse];
	g ← gToUse;
	time ← Time.Current[];
	-- g.msgout.PutF["\n\n"];
	errors ← CompileIfNecessary[g, modRepl
		! UNWIND => [] ← StopBatchCompile[]];
	g.ttyout.PutF["Elapsed time for compile: %r\n", IO.card[Time.Current[] - time]];
	g.ttyout.PutF["--------------------------------\n"];
	ReleaseCompilerLock[];
	};

CompileIfNecessary: PROC[g: CT.Global, modRepl: BOOL] RETURNS[errors: BOOL] = TRUSTED {
	numberSuccessful, numberOfWarnings, numberOfErrors: CARDINAL;
	onePageSpace ← Space.Create[size: 1, parent: Space.virtualMemory];
	manyPageSize ← 20;
	manyPageSpace ← Space.Create[size: manyPageSize, parent: Space.virtualMemory];
	Space.CreateUniformSwapUnits[parent: manyPageSpace, size: 8];
	-- Space.MakeReadOnly[manyPageSpace];
	FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
		IF l.first.srcFileName = NIL THEN LOOP;
		IF g.ttyout.UserAbort[] THEN ERROR ABORTED;
		IF modRepl AND NOT l.first.possiblyBad AND l.first.bcdValid THEN 
			g.ttyout.PutF["Skipping %s.\n", IO.rope[l.first.srcFileName]]
		ELSE IF l.first.dependencyBad THEN 
			g.ttyout.PutF["Skipping %s, something failed before it.\n", IO.rope[l.first.srcFileName]]
		ELSE IF BcdNoGood[l.first] THEN {
			g.ttyout.PutF["%s needs to be recompiled.\n", IO.rope[l.first.srcFileName]];
			ArrangeForCompile[l.first ! UNWIND => [] ← StopBatchCompile[]];
			IF l.first.bcdValid THEN
				SetPossiblyBadAndValid[l.first];
			RemoveBcdTabFile[l.first.bcdFileName];
			}
		ELSE {
			l.first.possiblyBad ← FALSE;
			g.ttyout.PutF["%s does not need to be recompiled.\n", IO.rope[l.first.srcFileName]];
			};
		ENDLOOP;
	[numberSuccessful, numberOfWarnings, numberOfErrors] ← StopBatchCompile[];
	IF numberSuccessful = 0 AND numberOfErrors = 0 AND numberOfWarnings = 0 THEN
		g.ttyout.PutF["Nothing was compiled.\n"]
	ELSE {
		g.ttyout.PutF["%d successful; ", IO.card[numberSuccessful]];
		IF numberOfErrors > 0 THEN g.ttyout.PutF["%d w/errors; ", IO.card[numberOfErrors]];
		IF numberOfWarnings > 0 THEN g.ttyout.PutF["%d w/warnings; ", IO.card[numberOfWarnings]];
		g.ttyout.PutF["\n"];
		};
	Space.Delete[onePageSpace];
	Space.Delete[manyPageSpace];
	errors ← numberOfErrors > 0;
	};	

BcdNoGood: PROC[mi: CT.MI] RETURNS[badBcd: BOOL] = TRUSTED {
	namestring: BcdOps.NameString;
	innerBcdBase: BcdOps.BcdBase;

	ForEachDirectory: PROC[fth: BcdOps.FTHandle, fti: BcdDefs.FTIndex] 
		RETURNS[stop: BOOL ← FALSE] = TRUSTED {
		bcdTab: CT.BcdTab;
		innerCap: File.Capability;
		name: Rope.Text ← NameToRope[fth.name, namestring];
		stop ← FALSE;
		name ← AppendExtension[name, ".bcd"L];
		AddDependsInfo[mi, name];
		bcdTab ← LookupBcdTabFile[name];
		IF bcdTab ~= NIL THEN {
			IF bcdTab.bcdVers ~= fth.version THEN badBcd ← TRUE;
			g.dout.PutF["Hit on %s\n", IO.rope[name]];
			RETURN;
			};
		-- must look on disk
		innerCap ← Directory.Lookup[fileName: LOOPHOLE[name],
			permissions: Directory.ignore
				! Directory.Error => GOTO fileNotFound];
		Space.Map[space: onePageSpace, window: [file: innerCap, base: 1]];
		AddToBcdTabFile[name, innerCap, innerBcdBase.version];
		IF innerBcdBase.version ~= fth.version THEN {
			badBcd ← TRUE;
			Space.Unmap[onePageSpace];
			RETURN[TRUE];
			};
		Space.Unmap[onePageSpace];
		EXITS
		fileNotFound => NULL;		-- can't tell if needs to be recompiled
		};
	
	{
	bcdBase: BcdOps.BcdBase;
	npages: CARDINAL;
	mth: BcdOps.MTHandle;
	badBcd ← FALSE;
	mi.srcCap ← Directory.Lookup[fileName: LOOPHOLE[mi.srcFileName], permissions: Directory.ignore
				! Directory.Error => GOTO srcNotFound];
	[create: mi.srcCreate] ← FileStream.GetLeaderPropertiesForCapability[mi.srcCap];
	mi.bcdCap ← Directory.Lookup[fileName: LOOPHOLE[mi.bcdFileName], permissions: Directory.ignore
				! Directory.Error => GOTO bad];
	Space.Map[space: onePageSpace, window: [file: mi.bcdCap, base: 1]];
	bcdBase ← Space.LongPointer[onePageSpace];
	IF bcdBase.sourceVersion.time ~= mi.srcCreate THEN badBcd ← TRUE;
	npages ← bcdBase.nPages;
	Space.Unmap[onePageSpace];
	-- now map in the right number of pages
	IF npages > manyPageSize THEN {
		Space.Delete[manyPageSpace];
		manyPageSize ← npages + 10;
		manyPageSpace ← Space.Create[size: manyPageSize, parent: Space.virtualMemory];
		Space.CreateUniformSwapUnits[parent: manyPageSpace, size: 8];
		-- Space.MakeReadOnly[manyPageSpace];
		};
	Space.Map[space: manyPageSpace, window: [file: mi.bcdCap, base: 1]];
	bcdBase ← Space.LongPointer[manyPageSpace];
	mth ← @(LOOPHOLE[bcdBase, BcdDefs.Base] + bcdBase.mtOffset)[FIRST[BcdDefs.MTIndex]];
	IF NOT bcdBase.definitions AND  
     (mi.switches['b] ~= mth.boundsChecks		-- there are some omissions here
      OR mi.switches['c] ~= mth.long		-- encoded in the old l
      OR mi.switches['j] ~= mth.crossJumped
      OR mi.switches['l] ~= (mth.linkLoc = code)	-- new interp. of /l
      OR mi.switches['n] ~= mth.nilChecks
      OR ((mi.switches['s] = mth.initial) AND mi.explicitSortSwitch))
    THEN badBcd ← TRUE;
	-- remember /s switch, initial = FALSE is /s, initial = TRUE is /-s
	-- if not explicitSortSwitch then set switches to whatever old bcd had
	IF NOT mi.explicitSortSwitch THEN
		mi.switches['s] ← NOT mth.initial;
	IF badBcd THEN {
		Space.Unmap[manyPageSpace];
		GOTO bad;	-- down here to make sure /s switch is known
		};
	namestring ← LOOPHOLE[bcdBase + bcdBase.ssOffset];
	innerBcdBase ← Space.LongPointer[onePageSpace];
	[] ← BcdOps.ProcessFiles[bcdBase, ForEachDirectory];
	IF NOT badBcd THEN {
		AddToBcdTabFile[mi.bcdFileName, mi.bcdCap, bcdBase.version];
		mi.bcdVers ← bcdBase.version;
		mi.definitions ← bcdBase.definitions;
		};
	Space.Unmap[manyPageSpace];
	EXITS
	bad => badBcd ← TRUE;
	srcNotFound => g.ttyout.PutF["Cannot open %s.\n", IO.rope[mi.srcFileName]];	-- what to do?
	}};
	
		
ArrangeForCompile: PROC[mi: CT.MI] = TRUSTED {
	errors, warnings, replaceable, declined: BOOL;
	IF mi.loadInfoSeq ~= NIL AND mi.loadInfoSeq.size = 1 THEN {
		-- try for replacement
		oldBcdFileName: Rope.Text;
		replaceResult: CTLoad.ReplaceResult;
		oldBcdFileName ← GenUniqueBcdName[mi.bcdFileName];
		SELECT TRUE FROM
			CS.EquivalentRope[oldBcdFileName, mi.bcdFileName] => 
				replaceResult ← cantCopyOldBcd;
			NOT RTOS.CheckForModuleReplacement[mi.loadInfoSeq[0].frame] =>
				replaceResult ← checkForMRFailed;
			ENDCASE => replaceResult ← ok;
		IF replaceResult ~= ok THEN {
			g.ttyout.PutF["%s cannot be replaced because %s.\n", 
					IO.rope[mi.bcdFileName],
					IO.rope[SELECT replaceResult FROM
						cantCopyOldBcd => "can't copy old bcd",
						checkForMRFailed => "RT check for module replacement failed",
						ENDCASE => ERROR]];
			declined ← TRUE;
			GOTO skip;
			};
		Directory.Rename[newName: LOOPHOLE[oldBcdFileName],
			oldName: LOOPHOLE[mi.bcdFileName]];
		RemoveBcdTabFile[mi.bcdFileName];
		mi.bcdCap ← File.nullCapability;
		g.ttyout.PutF["Old version of %s renamed to %s.\n", IO.rope[mi.bcdFileName],
				IO.rope[oldBcdFileName]];
		[errors, warnings, replaceable, declined] ← CompileIt[mi, TRUE, oldBcdFileName];
		IF NOT replaceable THEN replaceResult ← compilerSaysNo;
		IF replaceable AND NOT errors AND NOT declined THEN {
			g.ttyout.PutF["%s passes compiler's test for replaceability.\n", IO.rope[mi.bcdFileName]];
			mi.loadInfoSeq.mustreplace ← TRUE;
			}
		ELSE {
			mi.loadInfoSeq.mustreplace ← FALSE;
			IF declined OR errors THEN {
				-- new version has to be deleted
				Directory.Rename[newName: LOOPHOLE[mi.bcdFileName],
					oldName: LOOPHOLE[oldBcdFileName]];
				g.ttyout.PutF["Old, loaded version of %s has been left on disk.\n", 
						IO.rope[mi.bcdFileName]];
				}
			ELSE g.ttyout.PutF["%s is not replaceable%s, new version has been left on disk, \n\told loaded version is called %s.\n", 
					IO.rope[mi.bcdFileName], 
					IO.rope[IF replaceResult = compilerSaysNo THEN " (Compiler refuses)" ELSE ""],
					IO.rope[oldBcdFileName]];
			};
		EXITS
		skip => NULL;
		}
	ELSE {
		[errors, warnings, , declined] ← CompileIt[mi, FALSE, NIL];
		};
	mi.bcdValid ← NOT errors;
	};
	

CompileIt: PROC[mi: CT.MI, tryreplacement: BOOL, oldBcdFileName: Rope.Text] 
	RETURNS[errors, warnings, replaceable, declined: BOOL]  = TRUSTED {
	inx: CARDINAL ← 1;
	t: CompilerOps.Transaction;
	cap: File.Capability;
	onestarttime: LONG CARDINAL;
	
	DirectoryBinding: PROC[formalId, formalType: FileParms.Name, defaultLocator: LONG STRING,
			binder: FileParms.BindingProc] = TRUSTED {
		idname: Rope.Text ← SubStringToRope[@formalId];
		ref: REF ReturnRecord;
		ref ← GetActualIdForFile[idname, ConvertUnsafe.ToRope[defaultLocator]];
		AddDependsInfo[mi, ref.bcdFileName];
		IF ref.found THEN {
			actual: FileParms.ActualId ← [version: ref.version, 
					locator: [base: LOOPHOLE[ref.bcdFileName], offset: 0,
			 			length: ref.bcdFileName.Length[]]];
			binder[actual];
			g.dout.PutF["match %g with %g of %v\n", 
				IO.rope[idname], IO.rope[ref.bcdFileName], CS.MakeTS[ref.version]];
			}
		ELSE 
			g.ttyout.PutF["Error - %s not found.\n", IO.rope[ref.bcdFileName]];
		};
	
	-- called after DirectoryBinding, except for hidden directory entries
	DirectoryAcquire: PROC[type: LongString.SubStringDescriptor, 
	  actual: FileParms.ActualId] RETURNS[symbolSpace: FileParms.SymbolSpace] = TRUSTED {
		ref: REF ReturnRecord;
		ref ← GetActualIdForFile[SubStringToRope[@type], SubStringToRope[@actual.locator]];
		IF ref.found THEN RETURN[ref.symbolSpace];
		g.ttyout.PutF["%s of %v not found.\n", 
			IO.rope[ref.bcdFileName], CS.MakeTS[actual.version]];
		RETURN[FileParms.nullSymbolSpace];
		};
	
	DeleteBadBcd: PROC = TRUSTED {
		IF t.objectName ~= NIL THEN Directory.DeleteFile[t.objectName];
		t.objectName ← NIL;
		RemoveBcdTabFile[mi.bcdFileName];
		};
	
	Cleanup: PROC = TRUSTED {
		IF t.sourceStream ~= NIL THEN Stream.Delete[t.sourceStream];
		t.sourceStream ← NIL;
		sourcesh ← NIL;
		};
	
	{
	ENABLE UNWIND => {
		DeleteBadBcd[];
		Cleanup[];
		};
	
	errors ← warnings ← declined ← TRUE;
	replaceable ← FALSE;
	t.sourceStream ← NIL;
	t.objectName ← NIL;
	IF AskTheUser[mi.srcFileName, mi.switches] THEN RETURN;
	declined ← FALSE;
	-- make sure the compiler is loaded, etc.
	IF NOT compilerStarted THEN StartBatchCompile[];
	-- set up Transaction record contents
	t.op ← IF tryreplacement THEN replace ELSE compile;
	t.source ← [version: [net: 0, host: 0, time: mi.srcCreate],
		locator: [base: LOOPHOLE[mi.srcFileName], offset: 0, 
			length: mi.srcFileName.Length[]]];
	cap ← Directory.UpdateDates[mi.srcCap, File.read];
	sourcesh ← t.sourceStream ← FileStream.Create[cap];
	t.fileParms ← [DirectoryBinding, DirectoryAcquire, DirectoryRelease, DirectoryForget];
	t.switches ← mi.switches;
	t.switches['s] ← mi.explicitSortSwitch;
	IF tryreplacement THEN {
		IF mi.bcdVers = TimeStamp.Null THEN ERROR;
		t.pattern ← [version: mi.bcdVers, 
		 	locator: [base: LOOPHOLE[oldBcdFileName], offset: 0, 
				  length: oldBcdFileName.Length[]]];
		-- if this is replacement, try to set sorting the same as the old bcd
		IF NOT mi.explicitSortSwitch THEN
			t.switches['s] ← mi.switches['s]
		}
	ELSE t.pattern ← FileParms.nullActual;
	t.objectName ← LOOPHOLE[mi.bcdFileName];
	mi.bcdCap ← t.objectFile ← CS.NewFile[mi.bcdFileName, CS.ReadWrite, 10];
	t.debugPass ← LAST[CARDINAL];
	t.getStream ← LogGetStream;
	t.startPass ← CompilerPass;
	PrintStartOne[@t];
	onestarttime ← Time.Current[];
	-- these are here to hide them from the user
	t.switches['d] ← TRUE;	-- debugging
	t.switches['g] ← FALSE;	-- log is always Compiler.Log
	-- actually call the Compiler!
	CompilerOps.DoTransaction[@t];
	PrintStopOne[@t, onestarttime];
	replaceable ← tryreplacement AND t.matched;
	errors ← t.nErrors # 0;
	warnings ← t.nWarnings # 0;
	IF errors THEN err ← err + 1;
	IF warnings THEN warn ← warn + 1;
	IF NOT errors AND NOT warnings THEN good ← good + 1;
	IF NOT errors THEN { 
		mi.bcdVers ← t.objectVersion;
		mi.definitions ← (t.objectBytes = 0);	-- kludge to tell if it is a Defs file
		}
	ELSE 	
		DeleteBadBcd[];
	Cleanup[];
	}};
	
-- local procedures
	
AddDependsInfo: PROC[mi: CT.MI, bcdName: Rope.Text] = {
	FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
		IF CS.EquivalentRope[l.first.bcdFileName, bcdName] THEN {
			-- add to l.first the information that mi depends on it
			l.first.dependedBy ← CONS[mi.bcdFileName, l.first.dependedBy];
			-- always set to TRUE, reset in SetPossiblyBadAndValid
			l.first.dependencyBad ← TRUE;
			RETURN;
			};
		ENDLOOP;
	-- may not be able to add because this is a defs file not in the system
	--	e.g. Rope.bcd, IO.Bcd
	};
	
SetPossiblyBadAndValid: PUBLIC SAFE PROC[mi: CT.MI] = CHECKED {
	found: BOOL;
	FOR l: LIST OF Rope.Text ← mi.dependedBy, l.rest UNTIL l = NIL DO
		found ← FALSE;
		FOR ml: CT.ModuleList ← g.moduleList, ml.rest UNTIL ml = NIL DO
			IF CS.EquivalentRope[l.first, ml.first.bcdFileName] THEN {
				ml.first.possiblyBad ← TRUE;
				ml.first.dependencyBad ← FALSE;
				found ← TRUE;
				EXIT;
				};
			ENDLOOP;
		IF NOT found THEN 
			g.ttyout.PutF["Error - SetPossiblyBad: Can't find entry for %s.\n", IO.rope[l.first]];
		ENDLOOP;
	};
	
FlushOldBcd: PROC[mi: CT.MI] = {
	RemoveBcdTabFile[mi.bcdFileName];
	mi.bcdCap ← File.nullCapability;
	mi.bcdVers ← TimeStamp.Null;
	mi.bcdValid ← FALSE;
	};
	
StartBatchCompile: PROC = TRUSTED {
	herald: STRING ← [100];
	good ← warn ← err ← 0;
	logsh ← NIL;
	timeCompilerStarted ← Time.Current[];
	Directory.DeleteFile["Compiler.Log"L
		! Directory.Error => CONTINUE];
	[] ← LogGetStream[log];	-- creates new log
	CompilerOps.AppendHerald[herald];
	g.ttyout.PutF["%s\n%t\n", IO.string[herald], IO.card[timeCompilerStarted]];
	logsh.PutF["%s\n%t\n", IO.string[herald], IO.card[timeCompilerStarted]];
	CompilerOps.Start[Heap.systemZone];
	compilerStarted ← TRUE;
	};
	
StopBatchCompile: PROC RETURNS[nOk, nWarn, nErr: CARDINAL] = TRUSTED {
	log: ViewerClasses.Viewer;
	IF NOT compilerStarted THEN RETURN[0, 0, 0];	-- noop call; compiler not running
	IF good # 0 THEN logsh.PutF[" %d successful; ", IO.card[good]];
	IF warn # 0 THEN logsh.PutF[" %d w/warnings; ", IO.card[warn]];
	IF err # 0 THEN logsh.PutF[" %d w/errors; ", IO.card[err]];
	timeCompilerStarted ← Time.Current[] - timeCompilerStarted;
	logsh.PutF["\nTotal elapsed time: %y\n", IO.card[timeCompilerStarted]];
	Stream.Delete[logpilotsh];
	logsh ← NIL;
	CompilerOps.Stop[];
	compilerStarted ← FALSE;
	log ← ViewerOps.FindViewer["Compiler.Log"];
	IF log ~= NIL THEN ViewerOps.RestoreViewer[log];
	IF warn > 0 OR err > 0 THEN {
		IF log ~= NIL THEN ViewerOps.OpenIcon[log]
		ELSE {
			g.msgout.PutChar['\n]; 
			CreateANewViewer["Compiler.log", g.ttyout];
			};
		};
	g.ttyout.PutF["End of compilation\n"];
	RETURN[good, warn, err];
	};
	
DirectoryRelease: PROC[ss: FileParms.SymbolSpace] = {};
	
DirectoryForget: PROC[actual: FileParms.ActualId] = {};
	
PrintStartOne: PROC[t: POINTER TO CompilerOps.Transaction] = TRUSTED {
	first: BOOL ← TRUE;
	standardSwitches: CompilerOps.LetterSwitches ← CompilerOps.DefaultSwitches[];
	g.msgout.PutF["Compiling: %s", IO.string[t.source.locator.base]];
	logsh.PutF["\nCommand: %s", IO.string[t.source.locator.base]];
	FOR c: CHAR IN ['a .. 'z] DO
		sd: BOOL ← IF c = 'p THEN FALSE ELSE standardSwitches[c];
		IF t.switches[c] ~= sd THEN {
			IF first THEN {
				first ← FALSE;
				g.msgout.PutChar['/];
				logsh.PutChar['/];
				};
			IF sd THEN {
				g.msgout.PutChar['-];
				logsh.PutChar['-];
				};
			g.msgout.PutChar[c];
			logsh.PutChar[c];
			};
		ENDLOOP;
	logsh.PutChar['\n];
	};
	
PrintStopOne: PROC[t: POINTER TO CompilerOps.Transaction,
		oneStartTime: LONG CARDINAL] = TRUSTED {
	-- first MsgSW
	IF t.nErrors > 0 THEN 
		g.msgout.PutF["%d errors", IO.card[t.nErrors]]
	ELSE g.msgout.PutF["no errors"];
	IF t.nWarnings > 0 THEN 
		g.msgout.PutF[", %d warnings", IO.card[t.nWarnings]];
	g.msgout.PutChar['\n];
	-- now log
	logsh.PutF["%s -- ", IO.string[t.source.locator.base]];
	IF t.nErrors > 0 THEN {
		logsh.PutF[" aborted, %d errors", IO.card[t.nErrors]];
		IF t.nWarnings > 0 THEN
			logsh.PutF[" and %d warnings", IO.card[t.nWarnings]];
		oneStartTime ← Time.Current[] - oneStartTime;
		logsh.PutF[", time: %y.\n\n", IO.card[oneStartTime]];
		}
	ELSE {
		oneStartTime ← Time.Current[] - oneStartTime;
		logsh.PutF["source tokens: %d, time: %y",
			IO.card[t.sourceTokens], IO.card[oneStartTime]];
		IF t.objectBytes > 0 THEN 
			logsh.PutF["\n  code bytes: %d, links: %d, global frame words: %d",
				IO.card[t.objectBytes], IO.card[t.linkCount], IO.card[t.objectFrameSize]];
		IF t.nWarnings > 0 THEN
			logsh.PutF["\n%d warnings", IO.card[t.nWarnings]];
		logsh.PutF["\n\n"];
		};
	};
	
CreateANewViewer: PROC [name: Rope.Text, out: IO.Handle] = {
	 viewer: ViewerClasses.Viewer;
	WindowManager.WaitCursor[];
	viewer ← ViewerOps.CreateViewer[flavor: $Text, info: [name: name,
			file: name, iconic: FALSE, column: left]];
	out.PutF["Created Viewer: %s\n", IO.rope[name]];
	-- ViewerOps.SetNewFile[viewer];
	WindowManager.UnWaitCursor[];
	};
	
AskTheUser: PROC[filename: Rope.Text, wantsw: CompilerOps.LetterSwitches] 
  RETURNS[declined: BOOL] = {
	ch: CHAR;
	dif: Rope.ROPE;
	declined ← TRUE;
	-- ask the user if he really wants it compiled
	g.ttyout.PutF["Compile %s", IO.rope[filename]];
	dif ← ProduceDifferentialSwitches[wantsw];
	IF NOT Rope.IsEmpty[dif] THEN g.ttyout.PutF["/%s", IO.rope[dif]];
	g.ttyout.PutF[" ... "];
	ch ← IF NOT g.confirmCompiles THEN 'y ELSE 'n;
	IF ch = 'n THEN ch ← CS.Confirm['y, g.ttyin, g.ttyout] ;
	IF ch = 'q THEN {
		g.ttyout.PutF["Quit.\n"];
		ERROR IO.UserAborted[];
		};
	IF ch = 'a THEN {
		declined ← FALSE;
		g.confirmCompiles ← FALSE;
		g.ttyout.PutF["All Yes.\n"];
		}
	ELSE IF ch = 'y THEN {
		declined ← FALSE;
		g.ttyout.PutF["Yes.\n"];
		}
	ELSE g.ttyout.PutF["No.\n"];
	};
	
ProduceDifferentialSwitches: PROC[sw: CompilerOps.LetterSwitches] RETURNS[dif: Rope.ROPE] = TRUSTED {
	standardSwitches: CompilerOps.LetterSwitches ← CompilerOps.DefaultSwitches[];
	FOR c: CHAR IN ['a .. 'z] DO
		sd: BOOL ← IF c = 'p THEN FALSE ELSE standardSwitches[c];
		IF sw[c] ~= sd THEN {
			IF sd THEN 
				dif ← Rope.Cat[dif, Rope.FromChar['-]];
			dif ← Rope.Cat[dif, Rope.FromChar[c]];
			};
		ENDLOOP;
	};

LogGetStream: PROC[sid: CompilerOps.StreamId] RETURNS[sh: Stream.Handle] = {
	IF sid = source THEN RETURN[sourcesh];	-- temporary
	IF sid ~= log THEN ERROR;
	IF logsh = NIL THEN TRUSTED {
		logpilotsh ← CS.NewStream["Compiler.Log", CS.Write];
		logsh ← IO.CreateProcsStream[IO.CreateRefStreamProcs[putChar: LogStreamPutChar], NIL];
		CS.SetPFCodes[logsh];
		};
	sh ← logpilotsh;
	};
	
LogStreamPutChar: SAFE PROC[self: IO.STREAM, char: CHAR] = TRUSTED {
	Stream.PutChar[logpilotsh, char];
	};
	
CompilerPass: PROC[p: CARDINAL] RETURNS[goOn: BOOL] = {
	goOn ← NOT g.ttyin.UserAbort[];
	g.msgout.PutF[". "];
	};
	
NameToRope: PROC[name: BcdDefs.NameRecord, namestring: BcdOps.NameString]
	RETURNS[rope: Rope.Text] = TRUSTED {
	r: Rope.ROPE ← NIL;
	FOR i: CARDINAL IN [0 .. namestring.size[name]) DO
		r ← r.Cat[Rope.FromChar[namestring.string.text[name + i]]];
		ENDLOOP;
	rope ← Rope.Flatten[r];
	};

AcquireCompilerLock: ENTRY PROC[g: CT.Global] = {
	ENABLE UNWIND => NULL;
	p: BOOL ← TRUE;
	WHILE compilerIsLocked DO
		IF p THEN {
			g.ttyout.PutF["Waiting for free Compiler ... "];
			p ← FALSE;
			};
		WAIT compilerWait;
		ENDLOOP;
	compilerIsLocked ← TRUE;
	IF NOT p THEN g.ttyout.PutF["ok\n"];
	};
	
ReleaseCompilerLock: ENTRY PROC = {
	ENABLE UNWIND => NULL;
	compilerIsLocked ← FALSE;
	NOTIFY compilerWait;
	};
	
GenUniqueBcdName: PUBLIC SAFE PROC[bcdFileName: Rope.Text] RETURNS[newName: Rope.Text] = TRUSTED {
	inx: CARDINAL ← 1;
	newName ← bcdFileName;
	DO
		newName ← RopeInline.InlineFlatten[
			IO.PutFR["%s.%d.Bcd$", IO.rope[bcdFileName], IO.card[inx]]];
		[] ← Directory.Lookup[fileName: LOOPHOLE[newName], permissions: Directory.ignore
			! Directory.Error => GOTO out];
		inx ← inx + 1;
		ENDLOOP;
	EXITS
	out => NULL;
	};

AddToBcdTabFile: PROC[bcdFileName: Rope.Text, cap: File.Capability, version: TimeStamp.Stamp] = {
	g.bcdTabList ← CONS[NEW[CT.BcdTabRecord
		← [bcdFileName: bcdFileName, bcdCap: cap, bcdVers: version]], g.bcdTabList];
	};
	
-- there may already be an entry for bcdFileName w/o a symbolSpace
AddToBcdTabSymbolSpace: PROC[bcdFileName: Rope.Text, cap: File.Capability, 
	version: TimeStamp.Stamp, symbolSpace: FileParms.SymbolSpace] = {
	g.bcdTabList ← CONS[NEW[CT.BcdTabRecord
		← [bcdFileName: bcdFileName, bcdCap: cap, 
		   bcdVers: version, symbolSpace: symbolSpace]], g.bcdTabList];
	};
	
LookupBcdTabFile: PROC[bcdFileName: Rope.Text] RETURNS [bcdTab: CT.BcdTab] = {
	FOR l: LIST OF CT.BcdTab ← g.bcdTabList, l.rest UNTIL l = NIL DO
		IF CS.EquivalentRope[l.first.bcdFileName, bcdFileName] THEN RETURN[l.first];
		ENDLOOP;
	RETURN[NIL];
	};
	
RemoveBcdTabFile: PROC[bcdFileName: Rope.Text] = TRUSTED {
	bcdTab: CT.BcdTab;
	WHILE (bcdTab ← LookupBcdTabFile[bcdFileName]) ~= NIL DO
		g.bcdTabList ← LOOPHOLE[List.DRemove[bcdTab, LOOPHOLE[g.bcdTabList]]];
		ENDLOOP;
	};
	
SubStringToRope: PROC[lp: LongString.SubString] RETURNS[rope: Rope.Text] = TRUSTED {
	r: Rope.ROPE ← NIL;
	FOR i: CARDINAL IN [0 .. lp.length) DO
		r ← r.Cat[Rope.FromChar[lp.base[lp.offset+i]]];
		ENDLOOP;
	rope ← Rope.Flatten[r];
	};

AppendExtension: PUBLIC SAFE PROC[name: Rope.ROPE, ext: LONG STRING]
	RETURNS[r: Rope.Text] = TRUSTED {
	r ← RopeInline.InlineFlatten[name];
	RETURN[
		IF NOT CS.EndsIn[r, ext] THEN 
			RopeInline.InlineFlatten[Rope.Cat[name, ConvertUnsafe.ToRope[ext]]] 
		ELSE r];
	};

-- to avoid long-ref containing unsafe warning message from compiler
ReturnRecord: TYPE = RECORD[
	found: BOOL ← FALSE,
	version: TimeStamp.Stamp ← TimeStamp.Null, 
	bcdFileName: Rope.Text ← NIL, 
	symbolSpace: FileParms.SymbolSpace ← FileParms.nullSymbolSpace
	];
		
GetActualIdForFile: PROC[typename: Rope.Text, fileNameHint: Rope.Text]
	RETURNS[ref: REF ReturnRecord] = TRUSTED {
	bcdBase: BcdOps.BcdBase;
	sgb: BcdDefs.Base;
	firstMth: BcdOps.MTHandle;
	cap: File.Capability;
	namestring: BcdOps.NameString;
	bcdTab: CT.BcdTab;
	ref ← NEW[ReturnRecord ← []];
	ref.bcdFileName ← fileNameHint;
	IF ref.bcdFileName.Length[] = 0 THEN
		ref.bcdFileName ← typename;
	IF NOT CS.EndsIn[ref.bcdFileName, "$"L] THEN
		ref.bcdFileName ← AppendExtension[ref.bcdFileName, ".Bcd"L];
	bcdTab ← LookupBcdTabFile[ref.bcdFileName];
	IF bcdTab ~= NIL AND bcdTab.symbolSpace ~= FileParms.nullSymbolSpace THEN {
		g.dout.PutF["Hit on %s\n", IO.rope[ref.bcdFileName]];
		ref↑ ← [TRUE, bcdTab.bcdVers, ref.bcdFileName, bcdTab.symbolSpace]; 
		RETURN;
		};
	IF bcdTab = NIL THEN
		cap ← Directory.Lookup[fileName: LOOPHOLE[ref.bcdFileName], permissions: Directory.ignore
				! Directory.Error => GOTO notFound]
	ELSE 
		cap ← bcdTab.bcdCap;
	bcdBase ← LoadUpBcd[cap];
	namestring ← LOOPHOLE[bcdBase + bcdBase.ssOffset];
	firstMth ← @(LOOPHOLE[bcdBase,BcdDefs.Base] + bcdBase.mtOffset)[FIRST[BcdDefs.MTIndex]];
	sgb ← LOOPHOLE[bcdBase, BcdDefs.Base] + bcdBase.sgOffset;
	ref.version ← bcdBase.version;
	ref.found ← TRUE;
	ref.symbolSpace ← [file: cap, span: [base: sgb[firstMth.sseg].base,
						pages: sgb[firstMth.sseg].pages]];
	AddToBcdTabSymbolSpace[ref.bcdFileName, cap, ref.version, ref.symbolSpace];
	Space.Unmap[manyPageSpace];
	EXITS
	notFound => ref.found ← FALSE;
	};
	
LoadUpBcd: PROC[cap: File.Capability] 
	RETURNS[bcdBase: BcdOps.BcdBase] = TRUSTED {
	npages: CARDINAL;
	IF cap = File.nullCapability THEN ERROR;
	Space.Map[space: manyPageSpace, window: [file: cap, base: 1]];
	bcdBase ← Space.LongPointer[manyPageSpace];
	npages ← bcdBase.nPages;
	IF npages > manyPageSize THEN {
		Space.Delete[manyPageSpace];
		-- now map in the right number of pages
		manyPageSize ← npages + 10;
		manyPageSpace ← Space.Create[size: manyPageSize, parent: Space.virtualMemory];
		Space.Map[space: manyPageSpace, window: [file: cap, base: 1]];
		bcdBase ← Space.LongPointer[manyPageSpace];
		Space.CreateUniformSwapUnits[parent: manyPageSpace, size: 8];
		-- Space.MakeReadOnly[manyPageSpace];
		};
	};


}.