-- SMP4Impl.mesa
-- last edit by Schmidt, May 18, 1983 4:12 pm
-- last edit by Satterthwaite, May 26, 1983 5:40 pm
-- code to run the compiler for the Cedar Modeller

DIRECTORY
  Atom: TYPE USING [GetPName, MakeAtom],
  BcdStamps: TYPE USING [Compute],
  CompilerOps: TYPE USING [
    AppendHerald, CompilerVersion, DefaultSwitches, DoTransaction,
    LetterSwitches, Start, Stop, StreamId, Transaction],
  CS: TYPE USING [
    Confirm, EndsIn, EqualRope, EquivalentRope, MakeTS, NewFile, NewStream,
    SetPFCodes, Write],
  Directory: TYPE USING [DeleteFile, Error, Handle, ignore, Lookup, Rename, UpdateDates],
  File: TYPE USING [Capability, nullCapability, read],
  FileParms: TYPE USING [
    ActualId, BindingProc, Name, nullActual, nullSymbolSpace, SymbolSpace],
  FileStream: TYPE USING [Create],
  Heap: TYPE USING [systemZone],
  IO: TYPE USING [
    card, CreateProcsStream, CreateRefStreamProcs, Handle, PutChar, PutF, PutFR,
    rope, STREAM, string, UserAbort, UserAborted],
  List: TYPE USING [Reverse],
  Loader: TYPE USING [Instantiate, Start],
  LongString: TYPE USING [SubString, SubStringDescriptor],
  PrincOps: TYPE USING [ControlModule],
  Rope: TYPE USING [Cat, Fetch, Flatten, FromChar, IsEmpty, Length, Lower, ROPE, Text],
  RopeInline: TYPE USING [InlineFlatten],
  RTOS: TYPE USING [CheckForModuleReplacement],
  Runtime: TYPE USING [IsBound],
  SMEval: TYPE USING [CompMod, CompModRecord, LoadMod],
  SMFI: TYPE USING [BcdFileInfo, BcdModuleRecord, SrcFileInfo],
  SMFIOps: TYPE USING [
    AllocateBcdFileInfo, ConstructFIBcd, EraseCacheEntryForBcd, GetExtFromParse,
    LookupBcdFileInfo, NewVersionOfBcd, PutExtInParse],
  SMLoad: TYPE USING [ReplaceResult],
  SMOps: TYPE USING [MS, PL],
  SMP4: TYPE USING [],
  SMSrcBcd: TYPE USING [AddBcdInfo],
  SMTree: TYPE Tree USING [Handle, Link],
  SMTreeOps: TYPE USING [OpName, NthSon, NSons, Scan, ScanSons],
  Stream: TYPE USING [Delete, Handle, PutChar],
  Time: TYPE USING [Current],
  TimeStamp: TYPE USING [Null, Stamp],
  ViewerClasses: TYPE USING [Viewer],
  ViewerOps: TYPE USING [CreateViewer, FindViewer, OpenIcon, RestoreViewer],
  WindowManager: TYPE USING [UnWaitCursor, WaitCursor];
	
-- this monitor locks the compiler

SMP4Impl: CEDAR MONITOR 
    IMPORTS
      Atom, BcdStamps, CompilerOps, CS, Directory, FileStream, Heap, IO, List, Loader,
      Rope, RopeInline, RTOS, Runtime, SMFIOps, SMOps, SMSrcBcd, SMTreeOps,
      Stream, Time, ViewerOps, WindowManager
    EXPORTS SMP4 ~ {
  OPEN Tree~~SMTree, TreeOps~~SMTreeOps;

 -- MDS usage
 -- all these variables are protected by the monitor
  compilerIsLocked: BOOL ← FALSE;
  compilerWait: CONDITION;
  logsh: IO.STREAM ← NIL;		-- out stream to Compiler.Log
  logpilotsh: Stream.Handle ← NIL;
  sourcesh: Stream.Handle;		-- in stream to source file
  msgsw: IO.STREAM;		-- out stream to print status messages
  inputsh: IO.STREAM ← NIL;		-- in stream from typescript
  good, warn, err: CARDINAL ← 0;
  compilerStarted: BOOL ← FALSE;
  timeCompilerStarted: LONG CARDINAL ← 0;
 -- endof MDS

  OuterCompEval: PUBLIC PROC[
	ms: SMOps.MS, t: Tree.Link, confirm: REF BOOL, replacement: BOOL] 
      RETURNS[errors: BOOL] ~ {
    ENABLE UNWIND => ReleaseCompilerLock[];
    time: LONG CARDINAL;
    numberSuccessful, numberOfWarnings, numberOfErrors: CARDINAL;
    errors ← FALSE;
    AcquireCompilerLock[];
    inputsh ← ms.in;
    msgsw ← ms.msgOut;
    TRUSTED {time ← Time.Current[]};
    TraverseTreeForCompile[ms, NARROW[t], confirm, replacement
	! UNWIND => {[] ← StopBatchCompile[]}];
    [numberSuccessful, numberOfWarnings, numberOfErrors] ← StopBatchCompile[];
    TRUSTED {time ← Time.Current[] - time};
    IF numberSuccessful = 0 AND numberOfErrors = 0 AND numberOfWarnings = 0 THEN
      ms.PL["Nothing was compiled.\n"L]
    ELSE {
      ms.out.PutF["%d successful; ", IO.card[numberSuccessful]];
      IF numberOfErrors > 0 THEN ms.out.PutF["%d w/errors; ", IO.card[numberOfErrors]];
      IF numberOfWarnings > 0 THEN
        ms.out.PutF["%d w/warnings; ", IO.card[numberOfWarnings]];
      ms.PL["\n"L]};
    ms.PL["\n"L];
    ms.out.PutF["Elapsed time for compile: %r\n", IO.card[time]];
    ms.out.PutF["--------------------------------\n"];
    ReleaseCompilerLock[];
    errors ← numberOfErrors > 0};

  AcquireCompilerLock: ENTRY PROC ~ {
    ENABLE UNWIND => {NULL};
    WHILE compilerIsLocked DO WAIT compilerWait ENDLOOP;
    compilerIsLocked ← TRUE};
	
  ReleaseCompilerLock: ENTRY PROC ~ {
    ENABLE UNWIND => {NULL};
    compilerIsLocked ← FALSE;
    NOTIFY compilerWait};
	
  TraverseTreeForCompile: PROC[
      ms: SMOps.MS, top: Tree.Handle, confirm: REF BOOL, replacement: BOOL] ~ {

      Consider: PROC[anode: Tree.Handle, oldLoadMod: SMEval.LoadMod] ~ {
	groupOrBind: Tree.Handle;
	firstSon: Tree.Link;
	TreeOps.ScanSons[anode, AnalSons];
	groupOrBind ← NARROW[TreeOps.NthSon[anode, 2]];
	IF TreeOps.OpName[groupOrBind] ~= $bind 
	 AND TreeOps.OpName[groupOrBind] ~= $group THEN
	  RETURN;
	firstSon ← TreeOps.NthSon[anode, 1];
	WITH firstSon SELECT FROM
	  fiSrc: SMFI.SrcFileInfo => {
	    compMod: SMEval.CompMod ~ PossibleRecomp[
	      ms, fiSrc, groupOrBind, confirm, replacement, oldLoadMod];
	    SMFIOps.PutExtInParse[anode, compMod]};
	  ENDCASE => NULL;	-- do nothing
	};
		
      -- ms is passed in
      AnalSons: TreeOps.Scan ~ {
	WITH t SELECT FROM
	  applyNode: Tree.Handle => {
	    SELECT applyNode.name FROM
	      $apply => {
		ext: Tree.Link ← SMFIOps.GetExtFromParse[applyNode];
		IF ext ~= NIL AND ISTYPE[ext, SMEval.LoadMod] THEN {
		  -- manages to skip outer loading apply node
		  AnalSons[TreeOps.NthSon[applyNode, 2]];
		  Consider[NARROW[TreeOps.NthSon[applyNode, 1]], NARROW[ext]]}
		ELSE {
		  IF ext ~= NIL THEN RETURN;	-- already analyzed
		  Consider[applyNode, NIL]};
		};
	      ENDCASE => TreeOps.ScanSons[applyNode, AnalSons]};
	  ENDCASE => NULL;
	};

      TreeOps.ScanSons[top, AnalSons]};
	
  PossibleRecomp: PROC[
  	ms: SMOps.MS, fiSrc: SMFI.SrcFileInfo, 
	groupOrBind: Tree.Handle, confirm: REF BOOL, replacement: BOOL,
	oldLoadMod: SMEval.LoadMod] 
      RETURNS[compMod: SMEval.CompMod] ~ TRUSTED {
    fiBcd: SMFI.BcdFileInfo;
    errors, declined: BOOL;
    directoryList: LIST OF REF FormalActual;
    bcdVers: TimeStamp.Stamp;
    switches: CompilerOps.LetterSwitches;
    expSortSwitch: BOOL;
    compMod ← NEW[SMEval.CompModRecord ← [fiSrc: fiSrc]];
    [bcdVers, directoryList, switches, expSortSwitch] ←
      ConstructBcdStampFromBinding[ms, fiSrc, groupOrBind];
    compMod.fiBcd ← SMFIOps.LookupBcdFileInfo[fiSrc.srcFileName, bcdVers];
    -- first scan existing projection database
    ms.out.PutF["Considering compilation of %s ..\n", IO.rope[compMod.fiSrc.srcFileName]];
    IF compMod.fiBcd ~= NIL THEN RETURN;	-- already exists
    -- see if on disk
    fiBcd ← SMFIOps.ConstructFIBcd[compMod.fiSrc.shortname, bcdVers];
    IF fiBcd.bcdPresent AND bcdVers = fiBcd.bcdVers THEN {
      compMod.fiBcd ← fiBcd; RETURN};
    IF ~fiBcd.bcdPresent THEN
      ms.out.PutF["Must compile %g since there is no .Bcd on the disk.\n",
	IO.rope[compMod.fiSrc.srcFileName]]
    ELSE 
      ms.out.PutF["Must compile because bcd on disk is stamped %a, and the newer version will be stamped %a.\n",
	CS.MakeTS[fiBcd.bcdVers], CS.MakeTS[bcdVers]];
    -- bcd is not ok or doesn't exist, must recompile
    [errors, declined] ← ArrangeForCompile[
      ms, compMod.fiSrc, groupOrBind, replacement,
      fiBcd, switches, directoryList, confirm, expSortSwitch, oldLoadMod];
    IF ~declined THEN {
      IF errors THEN {
	-- there were errors, remove any capabilities for it
	SMFIOps.EraseCacheEntryForBcd[fi: fiBcd];
	compMod ← NIL}
      ELSE {
	-- record new version and update cache
	[] ← SMFIOps.NewVersionOfBcd[fi: fiBcd];
	compMod.fiBcd ← fiBcd};
      }
    ELSE compMod ← NIL};

  ArrangeForCompile: PROC[
	ms: SMOps.MS, fiOuter: SMFI.SrcFileInfo, groupOrBind: Tree.Handle,
	tryreplacement: BOOL, fiBcd: SMFI.BcdFileInfo, switches: CompilerOps.LetterSwitches, 
	directoryList: LIST OF REF FormalActual, confirm: REF BOOL, expSortSwitch: BOOL,
	oldLoadMod: SMEval.LoadMod] 
      RETURNS[errors, declined: BOOL] ~ TRUSTED {
    warnings, replaceable: BOOL;
    errors ← declined ← warnings ← replaceable ← FALSE;
    IF oldLoadMod ~= NIL 
     AND oldLoadMod.loadInfoSeq ~= NIL
     AND oldLoadMod.loadInfoSeq.size = 1 THEN {
      -- try for replacement
      oldBcdFileName: Rope.Text ~ GenUniqueBcdName[fiBcd.bcdFileName];
      replaceResult: SMLoad.ReplaceResult ← (SELECT TRUE FROM
	CS.EquivalentRope[oldBcdFileName, fiBcd.bcdFileName] => $cantCopyOldBcd,
	~RTOS.CheckForModuleReplacement[oldLoadMod.loadInfoSeq[0].frame] =>
	  $checkForMRFailed,
	ENDCASE => $ok);
      IF replaceResult ~= $ok THEN {
	ms.out.PutF["%s cannot be replaced because %s.\n", 
	    IO.rope[fiBcd.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[fiBcd.bcdFileName]];
      fiBcd.bcdCap ← File.nullCapability;
      ms.out.PutF["Old version of %s renamed to %s.\n",
        IO.rope[fiBcd.bcdFileName], IO.rope[oldBcdFileName]];
      [errors, warnings, replaceable, declined] ← CompileIt[
        ms, fiOuter, groupOrBind, oldBcdFileName, TRUE,
        fiBcd, switches, directoryList, confirm, expSortSwitch];
      IF ~replaceable THEN replaceResult ← $compilerSaysNo;
      IF replaceable AND ~errors AND ~declined THEN {
	ms.out.PutF["%s passes compiler's test for replaceability.\n", IO.rope[fiBcd.bcdFileName]];
	oldLoadMod.loadInfoSeq.mustreplace ← TRUE}
      ELSE {
	oldLoadMod.loadInfoSeq.mustreplace ← FALSE;
	IF declined OR errors THEN {
	  -- new version has to be deleted
	  Directory.Rename[
	    newName: LOOPHOLE[fiBcd.bcdFileName], oldName: LOOPHOLE[oldBcdFileName]];
	  ms.out.PutF["Old, loaded version of %s has been left on disk.\n", 
	    IO.rope[fiBcd.bcdFileName]]}
	ELSE ms.out.PutF[
	  "%s is not replaceable%s, new version has been left on disk, \n\told loaded version is called %s.\n", 
	  IO.rope[fiBcd.bcdFileName], 
	  IO.rope[IF replaceResult = $compilerSaysNo THEN " (Compiler refuses)" ELSE ""],
	  IO.rope[oldBcdFileName]]};
      EXITS
	skip => NULL;
      }
    ELSE {
      [errors, warnings, , declined] ← CompileIt[
        ms, fiOuter, groupOrBind, NIL, FALSE,
        fiBcd, switches, directoryList, confirm, expSortSwitch]};
    };
	
  FormalActual: TYPE ~ RECORD[
    id: ATOM,
    actual: FileParms.ActualId,
    compMod: SMEval.CompMod];
	
  ConstructBcdStampFromBinding: PROC[
	ms: SMOps.MS, fiSrc: SMFI.SrcFileInfo, groupOrBind: Tree.Handle] 
      RETURNS[
	bcdVers: TimeStamp.Stamp, directoryList: LIST OF REF FormalActual,
	switches: CompilerOps.LetterSwitches, expSortSwitch: BOOL] ~ {
    inx: CARDINAL ← 1;
	
    ForEachFormal: TreeOps.Scan ~ {
      WITH t SELECT FROM
	declElem: Tree.Handle => {
	  SELECT declElem.name FROM
	    $declElem => {	-- id, compMod are passed in

	      ForEachActual: TreeOps.Scan ~ {
		WITH t SELECT FROM
		    bindElem: Tree.Handle => {
		      SELECT bindElem.name FROM
			$bindElem =>
			  IF TreeOps.NthSon[bindElem, 1] = id THEN {
			    innerApply: Tree.Link ← TreeOps.NthSon[bindElem, 2];
			    IF ISTYPE[innerApply, Rope.Text] THEN {
			      [switches, expSortSwitch] ← InterpolateSwitches[NARROW[innerApply]];
			      RETURN};
			    compMod ← NARROW[SMFIOps.GetExtFromParse[innerApply]];
			    IF compMod = NIL THEN {
			      -- consider this is the loading apply, try the compiling apply
			      first: Tree.Link ← TreeOps.NthSon[innerApply, 1];
			      IF ISTYPE[first, Tree.Handle]
			       AND TreeOps.OpName[first] = $apply THEN
				compMod ← NARROW[SMFIOps.GetExtFromParse[first]]; -- innerApply
			      };
			    };
			ENDCASE => NULL};
		    ENDCASE => NULL;
		};
		
	      id: ATOM;
	      compMod: SMEval.CompMod;
	      IF TreeOps.OpName[TreeOps.NthSon[declElem, 2]] ~= $type THEN RETURN;
	      id ← NARROW[TreeOps.NthSon[declElem, 1]];
	      compMod ← NIL;
	      IF TreeOps.OpName[groupOrBind] = $group THEN {
		innerApply: Tree.Link;
		DO
		  IF inx > TreeOps.NSons[groupOrBind] THEN EXIT;
		  innerApply ← TreeOps.NthSon[groupOrBind, inx];
		  WITH innerApply SELECT FROM
		    innerApplyNode: Tree.Handle => {
		      IF TreeOps.OpName[innerApplyNode] = $apply THEN {
			compMod ← NARROW[SMFIOps.GetExtFromParse[innerApply]];
			IF compMod = NIL THEN {
			  -- consider this is the loading apply, try the compiling apply
			  first: Tree.Link ← TreeOps.NthSon[innerApply, 1];
			  IF TreeOps.OpName[first] = $apply THEN
			    compMod ← NARROW[SMFIOps.GetExtFromParse[first]];
			  };
			EXIT};
		      -- else goes to next item
		      };
		    str: Rope.Text => [switches, expSortSwitch] ← InterpolateSwitches[str];
		    ENDCASE => NULL;
		  inx ← inx + 1;
		  ENDLOOP;
		inx ← inx + 1;	-- bump for next iteration
		IF compMod = NIL THEN { 
		  ms.out.PutF["No compMod for formal '%s'.\n", IO.rope[Atom.GetPName[id]]];
		  RETURN}
		}
	      ELSE TreeOps.ScanSons[groupOrBind, ForEachActual];
	      IF compMod = NIL THEN 
		ms.out.PutF["can't find %s in actual tree.\n", IO.rope[Atom.GetPName[id]]]
	      ELSE {
		actual: FileParms.ActualId;
		fiInner: SMFI.BcdFileInfo ~ compMod.fiBcd;
		actual ← [
		  version~fiInner.bcdVers, 
		  locator~[
		    base~LOOPHOLE[fiInner.bcdFileName],
		    offset~0, length~fiInner.bcdFileName.Length]];
		directoryList ← CONS[NEW[FormalActual ← [id, actual, compMod]], directoryList]};
	      };
	    ENDCASE => NULL};
	ENDCASE => NULL;
      };
		
    domain: Tree.Handle ~ NARROW[TreeOps.NthSon[fiSrc.type, 1]];
    stampList: LIST OF REF TimeStamp.Stamp;
    TRUSTED {switches ← CompilerOps.DefaultSwitches[]};
    expSortSwitch ← FALSE;
    TreeOps.ScanSons[domain, ForEachFormal];
    FOR l: LIST OF REF FormalActual ← directoryList, l.rest UNTIL l = NIL DO
      stampList ← CONS[NEW[TimeStamp.Stamp ← l.first.actual.version], stampList];
      ENDLOOP;
    -- stampList is now reversed in the correct order!!!
    TRUSTED {directoryList ← LOOPHOLE[List.Reverse[LOOPHOLE[directoryList]]]};
    switches['s] ← expSortSwitch;	-- prefer not sorted
    TRUSTED {bcdVers ← BcdStamps.Compute[
      fiSrc.srcCreate, switches, CompilerOps.CompilerVersion[], stampList]};
    ms.out.PutF[
      "For %s, the version stamp is %a\n", IO.rope[fiSrc.shortname], CS.MakeTS[bcdVers]];
    };

	
  CompileIt: UNSAFE PROC[
	ms: SMOps.MS, fiOuter: SMFI.SrcFileInfo, groupOrBind: Tree.Handle,
	oldBcdFileName: Rope.Text, tryreplacement: BOOL, fiBcd: SMFI.BcdFileInfo,
	switches: CompilerOps.LetterSwitches, 
	directoryList: LIST OF REF FormalActual, confirm: REF BOOL, expSortSwitch: BOOL] 
      RETURNS[errors, warnings, replaceable, declined: BOOL] ~ UNCHECKED {
    t: CompilerOps.Transaction;
    cap: File.Capability;
    onestarttime: LONG CARDINAL;
    loadedOk: BOOL;
	
    DirectoryBinding: PROC[
    	formalId, formalType: FileParms.Name, defaultLocator: LONG STRING,
	binder: FileParms.BindingProc] ~ TRUSTED {
      desiredName: Rope.Text ~ SubStringToRope[@formalId];
      desiredId: ATOM ~ Atom.MakeAtom[desiredName];
      FOR l: LIST OF REF FormalActual ← directoryList, l.rest UNTIL l = NIL DO
	IF l.first.id = desiredId THEN {
	  fiInner: SMFI.BcdFileInfo ~ l.first.compMod.fiBcd;
	  binder[l.first.actual];
	  ms.out.PutF["match %g with %g of %a\n", IO.rope[desiredName],
		IO.rope[fiInner.bcdFileName], CS.MakeTS[fiInner.bcdVers]];
	  RETURN};
	ENDLOOP;
      ms.out.PutF["\nError - '%s' not found on any parameter list.\n", IO.rope[desiredName]]};

   -- called after DirectoryBinding, except for hidden directory entries
    DirectoryAcquire: PROC[
	  type: LongString.SubStringDescriptor, actual: FileParms.ActualId]
	RETURNS[ss: FileParms.SymbolSpace] ~ TRUSTED {
      bcdFileName: Rope.Text;
      fiInner: SMFI.BcdFileInfo;
      FOR l: LIST OF REF FormalActual ← directoryList, l.rest UNTIL l = NIL DO
	IF l.first.actual.version = actual.version THEN
	  RETURN[FindSymbolSpace[ms, l.first.compMod.fiBcd, type]];
	ENDLOOP;
      -- not found
      bcdFileName ← SubStringToRope[@actual.locator];
      IF CS.EndsIn[bcdFileName, "."L] THEN
	bcdFileName ← bcdFileName.Flatten[len~bcdFileName.Length-1];
      fiInner ← SMFIOps.LookupBcdFileInfo[bcdFileName, actual.version];
      IF fiInner ~= NIL THEN {
	ss ← FindSymbolSpace[ms, fiInner, type];
	IF ss = FileParms.nullSymbolSpace THEN
	  ms.out.PutF["Can't get symbol space for type %s, file %s\n",
		IO.rope[SubStringToRope[@type]], IO.rope[bcdFileName]];
	RETURN[FindSymbolSpace[ms, fiInner, type]]};
      ms.out.PutF["%s of %v not found on parameter list.\n", 
		IO.rope[bcdFileName], CS.MakeTS[actual.version]];
      RETURN[FileParms.nullSymbolSpace]};
	
    DeleteBadBcd: UNSAFE PROC ~ {
      IF t.objectName ~= NIL THEN Directory.DeleteFile[t.objectName];
      t.objectName ← NIL};
	
    Cleanup: UNSAFE PROC ~ {
      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;
    t.switches ← switches;
    IF AskTheUser[ms, fiOuter.srcFileName, ~confirm↑, t.switches] THEN RETURN;
    declined ← FALSE;
    -- make sure the compiler is loaded, etc.
    IF ~compilerStarted THEN {
      loadedOk ← StartBatchCompile[ms];
      IF ~loadedOk THEN RETURN};
    -- set up Transaction record contents
    t.op ← IF tryreplacement THEN $replace ELSE $compile;
    t.source ← [
        version~[net~0, host~0, time~fiOuter.srcCreate],
	locator~[
	    base~LOOPHOLE[fiOuter.srcFileName],
	    offset~0, length~fiOuter.srcFileName.Length]];
    cap ← Directory.UpdateDates[fiOuter.srcCap, File.read];
    sourcesh ← t.sourceStream ← FileStream.Create[cap];
    t.fileParms ← [DirectoryBinding, DirectoryAcquire, DirectoryRelease, DirectoryForget];
    IF tryreplacement THEN {
      fiBcdForOld: SMFI.BcdFileInfo;
      IF fiBcd.bcdVers = TimeStamp.Null THEN ERROR;
      t.pattern ← [
          version~fiBcd.bcdVers, 
          locator~[base: LOOPHOLE[oldBcdFileName], offset~0, length~oldBcdFileName.Length]];
      fiBcdForOld ← SMFIOps.AllocateBcdFileInfo[];
      fiBcdForOld.bcdFileName ← oldBcdFileName;
      SMFIOps.NewVersionOfBcd[fiBcdForOld]}
    ELSE t.pattern ← FileParms.nullActual;
    t.objectName ← LOOPHOLE[fiBcd.bcdFileName];
    t.objectFile ← CS.NewFile[fiBcd.bcdFileName, CS.Write, 10];
    t.debugPass ← CARDINAL.LAST;
    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
    t.switches['s] ← expSortSwitch;
   -- actually call the Compiler!
    CompilerOps.DoTransaction[@t];
    PrintStopOne[ms, @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 ~errors AND NOT warnings THEN good ← good + 1;
    IF ~errors THEN fiBcd.bcdVers ← t.objectVersion ELSE DeleteBadBcd[];
    Cleanup[]}};

  FindSymbolSpace: PROC[
	ms: SMOps.MS, fiBcd: SMFI.BcdFileInfo, type: LongString.SubStringDescriptor]
      RETURNS[ss: FileParms.SymbolSpace] ~ TRUSTED {
    name: Rope.Text ~ SubStringToRope[@type];
    -- warning: this is a workaround, SHOULD NOT be calling SMSrcBcd.AddBcdInfo
    -- as it is only supposed to be called from SMFIImpl
    IF fiBcd.bcdInfo = NIL THEN SMSrcBcd.AddBcdInfo[ms, fiBcd];
    -- for replacement when old Bcd is needed
    IF name.IsEmpty THEN RETURN[fiBcd.bcdInfo.modules.first.symbolSpace];
    FOR mod: LIST OF SMFI.BcdModuleRecord ← fiBcd.bcdInfo.modules, mod.rest UNTIL mod = NIL DO
      IF CS.EqualRope[name, mod.first.moduleName] THEN
	RETURN[mod.first.symbolSpace];
      ENDLOOP;
    RETURN[FileParms.nullSymbolSpace]};
	

 -- local procedures

  StartBatchCompile: PROC[ms: SMOps.MS] RETURNS[loadedOk: BOOL] ~ TRUSTED {
    herald: STRING ← [100];
    good ← warn ← err ← 0;
    logsh ← NIL;
    loadedOk ← LoadCompiler[ms];
    timeCompilerStarted ← Time.Current[];
    IF ~loadedOk THEN RETURN;
    Directory.DeleteFile["Compiler.Log"L ! Directory.Error => {CONTINUE}];
    [] ← LogGetStream[log];	-- creates new log
    CompilerOps.AppendHerald[herald];
    ms.out.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] ~ {
    log: ViewerClasses.Viewer;
    IF ~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]];
    TRUSTED {timeCompilerStarted ← Time.Current[] - timeCompilerStarted};
    logsh.PutF["\nTotal elapsed time %y.\n", IO.card[timeCompilerStarted]];
    TRUSTED {Stream.Delete[logpilotsh]};
    logsh ← NIL;
    TRUSTED {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 {msgsw.PutChar['\n]; CreateANewViewer["Compiler.log", msgsw]}};
    msgsw.PutF["End of compilation\n"];
    msgsw ← NIL;
    RETURN[good, warn, err]};

  CreateANewViewer: PROC [name: Rope.Text, out: IO.STREAM] ~ {
     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]];
    WindowManager.UnWaitCursor[]};

  AskTheUser: PROC[
	ms: SMOps.MS, filename: Rope.Text,
	dontconfirm: BOOL, wantsw: CompilerOps.LetterSwitches]
      RETURNS[declined: BOOL] ~ {
    ch: CHAR;
    dif: Rope.ROPE;
    declined ← TRUE;
    -- ask the user if he really wants it compiled
    ms.out.PutF["Compile %s", IO.rope[filename]];
    dif ← ProduceDifferentialSwitches[wantsw];
    IF ~dif.IsEmpty THEN ms.out.PutF["/%s", IO.rope[dif]];
    ms.out.PutF[" ... "];
    ch ← IF dontconfirm THEN 'y ELSE 'n;
    IF ch = 'n THEN ch ← CS.Confirm['y, ms.in, ms.out] ;
    IF ch = 'q THEN {ms.PL["Quit.\n"L]; ERROR IO.UserAborted[]};
    IF ch = 'y THEN {declined ← FALSE; ms.PL["Yes.\n"L]}
    ELSE ms.PL["No.\n"L]};

  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 ← dif.Cat[Rope.FromChar['-]];
	dif ← dif.Cat[Rope.FromChar[c]]};
      ENDLOOP;
    };

  DirectoryRelease: UNSAFE PROC[ss: FileParms.SymbolSpace] ~ {};

  DirectoryForget: UNSAFE PROC[actual: FileParms.ActualId] ~ {};

  PrintStartOne: UNSAFE PROC[t: POINTER TO CompilerOps.Transaction] ~ UNCHECKED {
    first: BOOL ← TRUE;
    standardSwitches: CompilerOps.LetterSwitches ~ CompilerOps.DefaultSwitches[];
    msgsw.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; msgsw.PutChar['/]; logsh.PutChar['/]};
	IF sd THEN {msgsw.PutChar['-]; logsh.PutChar['-]};
	msgsw.PutChar[c];
	logsh.PutChar[c]};
	ENDLOOP;
    logsh.PutChar['\n]};

  PrintStopOne: UNSAFE PROC[
      ms: SMOps.MS,
      t: POINTER TO CompilerOps.Transaction, oneStartTime: LONG CARDINAL] ~ UNCHECKED {
    -- first MsgSW
    IF t.nErrors > 0 THEN msgsw.PutF["%d errors", IO.card[t.nErrors]]
    ELSE msgsw.PutF["no errors"];
    IF t.nWarnings > 0 THEN msgsw.PutF[", %d warnings", IO.card[t.nWarnings]];
    msgsw.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]];
      ms.PL["\n\n"L]};
    };

 -- not monitored properly
  LoadCompiler: PUBLIC PROC[ms: SMOps.MS] RETURNS[success: BOOL] ~ TRUSTED {
    cap: File.Capability;
    success ← TRUE;
    IF Runtime.IsBound[CompilerOps.Start] THEN RETURN[TRUE];  -- already loaded
    ms.PL["Loading Compiler ... "L];
      {
      ENABLE ANY => {ms.PL["failed.\n"L]; GOTO out};
      cm: PrincOps.ControlModule;
      cap ← Directory.Lookup["compiler.bcd"L];
      [cm~cm] ← Loader.Instantiate[file~cap, offset~1, codeLinks~TRUE];
      Loader.Start[cm];
      ms.PL["done.\n"L];
      EXITS
	out => success ← FALSE;
      }
    };

  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;
    IF sh = NIL THEN ERROR};

  LogStreamPutChar: PROC[self: IO.STREAM, char: CHAR] ~ TRUSTED {
    logpilotsh.PutChar[char]};

  CompilerPass: PROC[p: CARDINAL] RETURNS[goOn: BOOL] ~ {
    goOn ← ~inputsh.UserAbort;
    msgsw.PutChar['.]};

  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 ← r.Flatten[]};

  FoldInParms: PROC[parms: Rope.Text]
      RETURNS[switches: CompilerOps.LetterSwitches, explicitSortSwitch: BOOL] ~ {
    i: CARDINAL ← 0;
    on: BOOL;
    ch: CHAR;
     -- set defaults
    TRUSTED {switches ← CompilerOps.DefaultSwitches[]};
    -- switches['s] ← FALSE;	the modeller defaults to /-s
    explicitSortSwitch ← FALSE;
    IF parms # NIL THEN
      WHILE i < parms.Length DO
	on ← TRUE;
	IF parms.Fetch[i] = '- THEN {i ← i + 1; on ← FALSE;};
	ch ← Rope.Lower[parms.Fetch[i]];
	IF ch IN ['a .. 'z] THEN {
	  switches[ch] ← on;
	  IF ch = 's THEN explicitSortSwitch ← TRUE};
	i ← i + 1;
	ENDLOOP;
    };

  InterpolateSwitches: PROC[parms: Rope.Text]
      RETURNS[switches: CompilerOps.LetterSwitches, explicitSortSwitch: BOOL] ~ {
    i: CARDINAL ← 0;
    on: BOOL;
    ch: CHAR;
     -- set defaults
    TRUSTED {switches ← CompilerOps.DefaultSwitches[]};
    -- switches['s] ← FALSE;	the modeller defaults to /-s
    explicitSortSwitch ← FALSE;
    IF parms = NIL THEN RETURN;
    WHILE i < parms.Length DO
      on ← TRUE;
      IF parms.Fetch[i] = '- THEN {i ← i + 1; on ← FALSE};
      ch ← Rope.Lower[parms.Fetch[i]];
      IF ch IN ['a .. 'z] THEN {
	switches[ch] ← on;
	IF ch = 's THEN explicitSortSwitch ← TRUE};
      i ← i + 1;
      ENDLOOP;
    };

  GenUniqueBcdName: 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;
    };

  }.