-- CheckIncludes.Mesa
-- Last modified by Sandman on July 8, 1980 9:19 AM
-- Last modified by Lewis on 13-Apr-81 13:51:31
-- Last modified by Paul Rovner on May 2, 1983 11:43 am

DIRECTORY
Ascii USING [CR, SP, FF],
BcdDefs USING [
Base, BCD, CTIndex, CTNull, CTRecord, FTNull, FTRecord, FTSelf, MTIndex,
PackedString, SGIndex, SGRecord, VersionID, VersionStamp],
BcdOps USING [MTHandle, ProcessModules],
Commander USING [Register, CommandProc, Handle],
ConvertUnsafe USING [ToRope],
Directory USING [Error, ignore, Lookup],
Environment USING [PageCount, PageNumber],
File USING [Capability, nullCapability],
FileLists USING [
feb, ifb, ifdb, fileList, FE, FEnil, IncFile, InclusionDepth, IFnil,
NullStamp, NullTime, IncFileDesc, IFDnil, Initialize, Finalize,
InsertInUserList, IsInUserList, InsertInFileList, InsertIncludeFileItem,
EnumerateUserList, UserListLength],
IncludesSymTables USING [
LoadSymTables, mdb, mdLimit, ssb, ht, ObsoleteSymbolTable, ReleaseSymTables],
Inline USING [LowHalf],
IO USING [Handle, PutRope, PutChar, UserAbort, ResetUserAbort, UserAborted],
LongString USING [
AppendChar, AppendDecimal, AppendString, AppendSubString, EquivalentString,
SubString, SubStringDescriptor],
OutputDefs USING [
CloseOutput, OpenOutput, PutChar, PutCR, PutLongString, PutNumber,
PutString, PutTime],
Rope USING [ROPE, Length, Fetch],
Segments USING [
DeleteSegment, EnumerateDirectory, FHandle, FileFromSegment, FileNameProblem,
FPHandle, GetFileTimes, InsertFile, LockFile, MoveSegment, NewFile, NewSegment,
OldFileOnly, Read, SHandle, SegmentAddress, SwapIn, Unlock, UnlockFile],
Symbols USING [HTIndex, HTNull, MDIndex, MDRecord, NullFileIndex],
Time USING [Current, Packed];

CheckIncludes: MONITOR
IMPORTS
BcdOps, Commander, ConvertUnsafe, Directory, FileLists, IncludesSymTables, Inline, IO,
LongString, OutputDefs, Rope, Segments, Time =
BEGIN OPEN FileLists;

FilenameChars: CARDINAL = 39;

CR: CHARACTER = Ascii.CR;
SP: CHARACTER = Ascii.SP;
FF: CHARACTER = Ascii.FF;


outputFileName: STRING ← [FilenameChars + 1];
sourceCount, bcdCount: CARDINAL;
startTime: LONG CARDINAL;

inStream: IO.Handle;
outStream: IO.Handle;
param: Rope.ROPE;
cmdIndex: CARDINAL;


SwitchTypes: TYPE = -- default is /IO~C~M~N~P
{consistent, includes, multiple, order, pause, noCompIfNotOnDisk};
switches: ARRAY SwitchTypes OF BOOLEAN
[consistent: FALSE, includes: TRUE, multiple: FALSE, order: TRUE,
pause: FALSE, noCompIfNotOnDisk: FALSE];


AbortChecking: SIGNAL = CODE;

Ugly: PROC[rope: Rope.ROPE] =
{outStream.PutRope[rope]};

CheckFiles: ENTRY Commander.CommandProc = TRUSTED
--[cmd: Handle]
BEGIN ENABLE {UNWIND => NULL; IO.UserAborted, AbortChecking => GO TO Aborted};
Initialize[cmd];
PutHeading[];
GetOutputFileAndSwitches[];
GetInputFileNames[];
EchoCommands[];
AddUserSpecifiedFiles[];
CheckForSourceButNoBcd[];
MarkDirectBads[];
DoOutput[];
Finalize[];
EXITS
Aborted => {
ReleaseBcd[];
IncludesSymTables.ReleaseSymTables[];
FileLists.Finalize[];
cmd.out.PutRope["\n...aborted\n"]};
END;

Initialize: PROC [cmd: Commander.Handle]=
BEGIN
inStream ← cmd.in;
outStream ← cmd.out; -- Commander's TypeScript window
param ← cmd.commandLine;
cmdIndex ← 0;
FileLists.Initialize[];
startTime ← Time.Current[];
sourceCount ← bcdCount ← largestDepthCount ← 0;
badFilesExist ← lastFileCompiledHadSwitch ← FALSE;
bcdSeg ← NIL; bcd ← NIL;
symFile ← NIL
END;

Finalize: PROC =
BEGIN
stats: STRING ← [50];
FileLists.Finalize[];
LongString.AppendDecimal[stats, sourceCount];
LongString.AppendString[stats, " source files, "L];
LongString.AppendDecimal[stats, bcdCount];
LongString.AppendString[stats, " Bcds, "L];
LongString.AppendDecimal[stats, Inline.LowHalf[Time.Current[] - startTime]];
LongString.AppendString[stats, " seconds"L];
outStream.PutChar['\n];
Ugly[ConvertUnsafe.ToRope[stats]];
outStream.PutChar['\n];
END;


PutHeading: PROC =
BEGIN
Ugly["\nCedar IncludeChecker 1.0\n"];
END;


GetOutputFileAndSwitches: PROC =
BEGIN
GetToken[outputFileName];
SetSwitches[outputFileName];
IF outputFileName.length = 0 THEN
LongString.AppendString[outputFileName, "Includes"L];
END;

GetInputFileNames: PROC =
BEGIN
fileName: STRING ← [FilenameChars + 1];
GetToken[fileName];
UNTIL fileName.length = 0 DO
FileLists.InsertInUserList[fileName];
GetToken[fileName];
ENDLOOP;
END;

EchoCommands: PROC =
BEGIN
fullOutputName: STRING ← [40];
IF FileLists.UserListLength[] = 0 THEN
Ugly["Processing all sources and Bcds on the volume\n"];
IF switches[consistent] THEN Ugly["Compile/bind command to Line.cm\n"];
IF ~switches[multiple] THEN
BEGIN
LongString.AppendString[fullOutputName, outputFileName];
FOR i: CARDINAL IN [0..fullOutputName.length) DO
IF fullOutputName[i] = '. THEN {fullOutputName.length ← i; EXIT};
ENDLOOP;
LongString.AppendString[fullOutputName, ".list"L];
IF switches[order] THEN
{Ugly["Compilation order to "];
Ugly[ConvertUnsafe.ToRope[fullOutputName]]};
IF switches[includes] THEN
{Ugly["Includes/included by list to "];
Ugly[ConvertUnsafe.ToRope[fullOutputName]]};
END
ELSE
BEGIN
Ugly["Multiple output files "];
SELECT TRUE FROM
switches[order] AND switches[includes] =>
Ugly["with compilation order and includes/included by list\n"];
switches[order] =>
Ugly["with compilation order\n"];
switches[includes] =>
Ugly["with includes/included by list\n"];
ENDCASE => outStream.PutChar['\n];
END;
outStream.PutChar['\n];
END;

GetToken: PROC [token: STRING] =
BEGIN
c: CHARACTER;
token.length ← 0;
UNTIL cmdIndex >= Rope.Length[param] DO
c ← Rope.Fetch[param, cmdIndex];
cmdIndex ← cmdIndex + 1;
SELECT c FROM
SP, CR => IF token.length > 0 THEN RETURN;
ENDCASE => LongString.AppendChar[token, c];
ENDLOOP;
END;

SetSwitches: PROC [s: STRING] =
BEGIN
start: CARDINAL;
notMinus: BOOLEANTRUE;

FOR i: CARDINAL IN [0..s.length) DO
IF s[i] = '/ THEN {start ← i; EXIT};
REPEAT
FINISHED => RETURN;
ENDLOOP;
FOR i: CARDINAL IN (start..s.length) DO
SELECT s[i] FROM
'/ => LOOP;
'-, '~ => {notMinus ← FALSE; LOOP};
'c, 'C => switches[consistent] ← notMinus;
'i, 'I => switches[includes] ← notMinus;
'm, 'M => switches[multiple] ← notMinus;
'n, 'N => switches[noCompIfNotOnDisk] ← notMinus;
'o, 'O => switches[order] ← notMinus;
's, 'S =>
IF notMinus THEN -- star hack (=C~I~O)
BEGIN
switches[consistent] ← notMinus;
switches[includes] ← switches[order] ← ~notMinus;
END;
'p, 'P => switches[pause] ← notMinus;
ENDCASE => LOOP;
notMinus ← TRUE;
ENDLOOP;
s.length ← start;
END;


-- ADD USER-SPECIFIED FILES TO FILELIST, AND ANY FILES THEY DIRECTLY INCLUDE

AddUserSpecifiedFiles: PROC =
BEGIN
IF FileLists.UserListLength[] = 0 THEN -- process all files on volume
EnumerateDirectoryForFiles[]
ELSE LookupFilesDirectly[];
END;

EnumerateDirectoryForFiles: PROC =
BEGIN

CheckOneFile: PROC [
fp: Segments.FPHandle, dirName: STRING] RETURNS [stop: BOOLEAN] =
BEGIN
fileName: STRING ← [FilenameChars + 1];
ext: STRING ← [FilenameChars + 1];
mesa, config, bcd: BOOLEANFALSE;
file: Segments.FHandle;
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
SplitFileName[wholename: dirName, name: fileName, ext: ext];
IF LongString.EquivalentString[ext, "mesa"] THEN mesa ← TRUE
ELSE IF LongString.EquivalentString[ext, "config"] THEN config ← TRUE
ELSE IF LongString.EquivalentString[ext, "bcd"] THEN bcd ← TRUE
ELSE RETURN[FALSE];
IF FileLists.UserListLength[] = 0 OR FileLists.IsInUserList[fileName] THEN
BEGIN
file ← Segments.InsertFile[file: fp, access: Segments.Read];
Segments.LockFile[file];
IF mesa OR config THEN
AddSourceFile[sourceFile: file, fileName: fileName, config: config]
ELSE AddObjectFile[bcdFile: file, fileName: fileName];
Segments.UnlockFile[file];
END;
RETURN[FALSE]
END;

Segments.EnumerateDirectory[CheckOneFile];
END;

SplitFileName: PROC [wholename, name, ext: STRING] =
BEGIN
active: STRING ← name;
name.length ← ext.length ← 0;
FOR i: CARDINAL IN [0..wholename.length) DO
IF wholename[i] = '. THEN active ← ext
ELSE LongString.AppendChar[active, wholename[i]];
ENDLOOP;
END;


LookupFilesDirectly: PROC =
BEGIN

AddRequestedFile: PROC [fileName: LONG STRING] RETURNS [stop: BOOLEAN] =
BEGIN
sourceName: STRING ← [FilenameChars + 1];
bcdName: STRING ← [FilenameChars + 1];
sourceFile, objectFile: Segments.FHandle ← NIL;
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
LongString.AppendString[to: sourceName, from: fileName];
LongString.AppendString[to: sourceName, from: ".mesa"L];
sourceFile ← Segments.NewFile[
name: sourceName, access: Segments.Read,
version: Segments.OldFileOnly
! Segments.FileNameProblem[] => CONTINUE];
IF sourceFile # NIL THEN
BEGIN
Segments.LockFile[sourceFile];
AddSourceFile[
sourceFile: sourceFile, fileName: fileName, config: FALSE];
Segments.UnlockFile[sourceFile];
END
ELSE -- try for config with that name
BEGIN
sourceName.length ← 0;
LongString.AppendString[to: sourceName, from: fileName];
LongString.AppendString[to: sourceName, from: ".config"L];
sourceFile ← Segments.NewFile[
name: sourceName, access: Segments.Read,
version: Segments.OldFileOnly
! Segments.FileNameProblem[] => CONTINUE];
IF sourceFile # NIL THEN
BEGIN
Segments.LockFile[sourceFile];
AddSourceFile[
sourceFile: sourceFile, fileName: fileName, config: TRUE];
Segments.UnlockFile[sourceFile];
END;
END;
LongString.AppendString[to: bcdName, from: fileName];
LongString.AppendString[to: bcdName, from: ".bcd"L];
objectFile ← Segments.NewFile[
name: bcdName, access: Segments.Read,
version: Segments.OldFileOnly
! Segments.FileNameProblem[] => CONTINUE];
IF objectFile # NIL THEN
BEGIN
Segments.LockFile[objectFile];
AddObjectFile[objectFile, fileName];
Segments.UnlockFile[objectFile];
END;
RETURN[FALSE];
END;

FileLists.EnumerateUserList[AddRequestedFile];
END;


AddSourceFile: PROC [sourceFile: Segments.FHandle, fileName: LONG STRING, config: BOOLEAN] =
BEGIN
fe: FileLists.FE;
outStream.PutChar['.]; sourceCount ← sourceCount+1;
fe ← FileLists.InsertInFileList[fileName];
feb[fe].source ← TRUE; feb[fe].config ← config;
feb[fe].sourceTime ← Segments.GetFileTimes[file: sourceFile].create;
END;


ObsoleteBcd: ERROR = CODE; -- Bcd or symbol table version not current
CompilerSmashedBcd: ERROR = CODE; -- File had compilation errors
ErroneousBcd: ERROR = CODE;

bcdSeg: Segments.SHandle ← NIL;
bcd: LONG POINTER TO BcdDefs.BCDNIL;
symFile: Segments.FHandle ← NIL;
symBase, symSize: Environment.PageNumber;

AddObjectFile: PROC [bcdFile: Segments.FHandle, fileName: LONG STRING] =
BEGIN
bcdVersion: BcdDefs.VersionStamp;
bcdSourceTime: Time.Packed;
defModule, dStar, crossJumped, longAlto, tableCompiled, config: BOOLEAN;
fe: FileLists.FE;
BEGIN
[bcdVersion, bcdSourceTime, defModule, dStar, crossJumped, longAlto, tableCompiled, config] ←
ExamineBcd[bcdFile
! ObsoleteBcd => GOTO obsoleteBcd;
CompilerSmashedBcd => GOTO compilerSmashedBcd;
ANY => GOTO processingError];
IF ~config THEN {
ReleaseBcd[];
IF symFile # NIL THEN
IncludesSymTables.LoadSymTables[symFile, symBase, symSize
! IncludesSymTables.ObsoleteSymbolTable => GOTO obsoleteBcd]
ELSE IF ~tableCompiled THEN GOTO ignoreBcd};
outStream.PutChar['.]; bcdCount ← bcdCount+1;
fe ← FileLists.InsertInFileList[fileName];
feb[fe].stamp ← bcdVersion;
feb[fe].bcdSourceTime ← bcdSourceTime;
feb[fe].tableCompiled ← tableCompiled;
IF defModule THEN feb[fe].depth ← 50 + (feb[fe].depth MOD 50);
-- if defs file was included by another defs file
feb[fe].config ← config;
IF config THEN
BEGIN
ProcessIncludedModules[includer: fe, includerName: fileName];
ReleaseBcd[];
END
ELSE
BEGIN
feb[fe].dStar ← dStar;
feb[fe].crossJumped ← crossJumped;
feb[fe].longAlto ← longAlto;
IF symFile # NIL THEN {
ProcessIncludedFiles[fe, fileName ! ErroneousBcd => GOTO erroneousBcd];
IncludesSymTables.ReleaseSymTables[];
IF symFile # bcdFile THEN Segments.UnlockFile[symFile]};
END;
EXITS
ignoreBcd => NULL;
obsoleteBcd =>
BEGIN
Ugly["\n "];
Ugly[ConvertUnsafe.ToRope[fileName]];
Ugly[".bcd has an incompatible version\n"];
fe ← FileLists.InsertInFileList[fileName]; feb[fe].obsolete ← TRUE;
END;
compilerSmashedBcd =>
BEGIN
Ugly["\n "];
Ugly[ConvertUnsafe.ToRope[fileName]];
Ugly[".bcd was marked invalid due to compilation errors\n"];
fe ← FileLists.InsertInFileList[fileName]; feb[fe].erroneous ← TRUE;
END;
processingError =>
BEGIN
Ugly["\n "];
Ugly[ConvertUnsafe.ToRope[fileName]];
Ugly[".bcd -- processing error (bad Bcd?)\n"];
END;
erroneousBcd =>
BEGIN
Ugly["\n "];
Ugly[ConvertUnsafe.ToRope[fileName]];
Ugly[".bcd is invalid\n"];
IncludesSymTables.ReleaseSymTables[];
IF symFile # bcdFile THEN Segments.UnlockFile[symFile];
END;
END;
END;

ExamineBcd: PROC [bcdFile: Segments.FHandle]
RETURNS [
bcdVersion: BcdDefs.VersionStamp, bcdSourceTime: Time.Packed,
defModule, dStar, crossJumped, longAlto, tableCompiled, config: BOOLEAN] =
BEGIN
bcdPages: Environment.PageCount;
mtb: BcdDefs.Base;
modulesMti: BcdDefs.MTIndex = FIRST[BcdDefs.MTIndex];
bcdSeg ← Segments.NewSegment[file: bcdFile, base: 1, pages: 10, access: Segments.Read];
Segments.SwapIn[bcdSeg];
bcd ← Segments.SegmentAddress[bcdSeg];
IF (bcdPages ← bcd.nPages) > 10 THEN
BEGIN
Segments.Unlock[bcdSeg];
Segments.MoveSegment[seg: bcdSeg, base: 1, pages: bcdPages];
Segments.SwapIn[bcdSeg];
bcd ← Segments.SegmentAddress[bcdSeg];
END;
IF bcd.versionIdent # BcdDefs.VersionID THEN
IF bcd.versionIdent = FileLists.NullStamp.time THEN
ERROR CompilerSmashedBcd[ ! UNWIND => ReleaseBcd[]]
ELSE ERROR ObsoleteBcd[ ! UNWIND => ReleaseBcd[]];
config ← (bcd.nConfigs # 0);
mtb ← LOOPHOLE[bcd + bcd.mtOffset];
bcdVersion ← bcd.version;
bcdSourceTime ← LOOPHOLE[bcd.sourceVersion.time, Time.Packed];
defModule ← bcd.definitions;
tableCompiled ← bcd.tableCompiled;
IF ~config THEN {
dStar ← ~mtb[modulesMti].altoCode;
crossJumped ← mtb[modulesMti].crossJumped;
longAlto ← mtb[modulesMti].long AND mtb[modulesMti].altoCode;
FindSymbolSeg[bcdSeg: bcdSeg, sgi: mtb[modulesMti].sseg ! UNWIND => ReleaseBcd[]]}
ELSE {symFile ← NIL; symBase ← symSize ← 0};
END;

ReleaseBcd: PROC = {
IF bcdSeg # NIL THEN {
Segments.Unlock[bcdSeg]; Segments.DeleteSegment[bcdSeg]; bcdSeg ← NIL;
bcd ← NIL}};

FindSymbolSeg: PROC [bcdSeg: Segments.SHandle, sgi: BcdDefs.SGIndex] =
BEGIN
segHandle: LONG POINTER TO BcdDefs.SGRecord ←
@LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][sgi];
IF segHandle.file = BcdDefs.FTNull THEN {symFile ← NIL; symBase ← symSize ← 0}
ELSE
BEGIN
symBase ← segHandle.base; symSize ← segHandle.pages;
IF segHandle.file = BcdDefs.FTSelf THEN symFile ← Segments.FileFromSegment[bcdSeg]
ELSE
BEGIN
f: LONG POINTER TO BcdDefs.FTRecord =
@LOOPHOLE[bcd + bcd.ftOffset, BcdDefs.Base][segHandle.file];
ssb: LONG POINTER TO BcdDefs.PackedString ← LOOPHOLE[bcd + bcd.ssOffset];
ss: LongString.SubStringDescriptor ← [
base: @ssb.string, offset: f.name, length: ssb.size[f.name]];
symFileName: STRING ← [FilenameChars + 1];
LongString.AppendSubString[symFileName, @ss];
AddExtension[symFileName, ".bcd"];
symFile ← Segments.NewFile[
name: symFileName, access: Segments.Read, version: Segments.OldFileOnly];
Segments.LockFile[symFile];
END;
END;
END;

AddExtension: PROC [name, ext: STRING] =
BEGIN
FOR i: CARDINAL IN [0..name.length) DO
IF name[i] = '. THEN RETURN;
ENDLOOP;
LongString.AppendString[name, ext];
END;

ProcessIncludedFiles: PROC [includer: FileLists.FE, includerName: LONG STRING] =
BEGIN
secondMdi: Symbols.MDIndex = (FIRST[Symbols.MDIndex] + SIZE[Symbols.MDRecord]);
includedFile: FileLists.FE;
includedName: STRING ← [FilenameChars + 1];
ss: LongString.SubStringDescriptor;
FOR mdi: Symbols.MDIndex ← secondMdi, (mdi + SIZE[Symbols.MDRecord])
UNTIL mdi >= IncludesSymTables.mdLimit DO
SubStringForHash[@ss, IncludesSymTables.mdb[mdi].fileId];
IF ss.length > (FilenameChars + 1) THEN ERROR ErroneousBcd;
includedName.length ← 0;
FOR i: CARDINAL IN [0..ss.length) DO
IF ss.base[ss.offset + i] = '. THEN EXIT;
LongString.AppendChar[includedName, ss.base[ss.offset + i]];
ENDLOOP;
includedFile ← FileLists.InsertInFileList[includedName];
feb[includer].includes ← FileLists.InsertIncludeFileItem[
incList: feb[includer].includes,
fe: includedFile, feName: includedName,
stamp: IncludesSymTables.mdb[mdi].stamp,
fileOpenedByCompiler: -- were items actually read by Compiler
(IncludesSymTables.mdb[mdi].file # Symbols.NullFileIndex)];
IncreaseDepth[root: includedFile, minDepth: (feb[includer].depth + 1)];
feb[includedFile].includedBy ← FileLists.InsertIncludeFileItem[
incList: feb[includedFile].includedBy,
fe: includer, feName: includerName,
stamp: FileLists.NullStamp,
fileOpenedByCompiler: FALSE];
ENDLOOP;
END;

ProcessIncludedModules: PROC [includer: FileLists.FE, includerName: LONG STRING] =
BEGIN
cti, parentCti: BcdDefs.CTIndex;
ctb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.ctOffset];
ftb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.ftOffset];
mtb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.mtOffset];
includee: FileLists.FE;
includeeName: STRING ← [FilenameChars + 1];
includeeStamp: BcdDefs.VersionStamp;
names: LONG POINTER TO BcdDefs.PackedString ← LOOPHOLE[bcd + bcd.ssOffset];
name: CARDINAL;
FOR cti ← FIRST[BcdDefs.CTIndex], (cti + SIZE[BcdDefs.CTRecord] + ctb[cti].nControls)
UNTIL cti = bcd.ctLimit DO
IF ctb[cti].config = BcdDefs.CTNull THEN {parentCti ← cti; LOOP};
includeeName.length ← 0;
name ← ctb[cti].name;
FOR i: CARDINAL IN [name..name+names.size[name]) DO
LongString.AppendChar[includeeName, names.string[i]];
ENDLOOP;
includee ← FileLists.InsertInFileList[includeeName];
includeeStamp ←
(IF ctb[cti].file = BcdDefs.FTSelf THEN bcd.version ELSE ftb[ctb[cti].file].version);
feb[includer].includes ← FileLists.InsertIncludeFileItem[
incList: feb[includer].includes, fe: includee, feName: includeeName,
stamp: includeeStamp,
fileOpenedByCompiler: TRUE ! UNWIND => ReleaseBcd[]];
IncreaseDepth[root: includee, minDepth: (feb[includer].depth + 1)];
feb[includee].includedBy ← FileLists.InsertIncludeFileItem[
incList: feb[includee].includedBy, fe: includer, feName: includerName,
stamp: FileLists.NullStamp,
fileOpenedByCompiler: FALSE ! UNWIND => ReleaseBcd[]];
ENDLOOP;

{DoModule: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOLEAN] = {
IF mth.config # parentCti THEN RETURN[FALSE];
includeeName.length ← 0;
name ← mth.name;
FOR i: CARDINAL IN [name..name+names.size[name]) DO
LongString.AppendChar[includeeName, names.string[i]];
ENDLOOP;
includee ← FileLists.InsertInFileList[includeeName];
includeeStamp ←
(IF mth.file = BcdDefs.FTSelf THEN bcd.version ELSE ftb[mth.file].version);
feb[includer].includes ← FileLists.InsertIncludeFileItem[
incList: feb[includer].includes, fe: includee, feName: includeeName,
stamp: includeeStamp,
fileOpenedByCompiler: TRUE ! UNWIND => ReleaseBcd[]];
IncreaseDepth[includee, (feb[includer].depth + 1)];
feb[includee].includedBy ← FileLists.InsertIncludeFileItem[
incList: feb[includee].includedBy, fe: includer, feName: includerName,
stamp: FileLists.NullStamp,
fileOpenedByCompiler: FALSE ! UNWIND => ReleaseBcd[]];
RETURN[FALSE]};

[] ← BcdOps.ProcessModules[bcd, DoModule]};

END;

SubStringForHash: PROC [ss: LongString.SubString, hti: Symbols.HTIndex] =
BEGIN
ss.base ← IncludesSymTables.ssb;
IF hti = Symbols.HTNull THEN ss.offset ← ss.length ← 0
ELSE
BEGIN
ss.offset ← IncludesSymTables.ht[hti - 1].ssIndex;
ss.length ← (IncludesSymTables.ht[hti].ssIndex - ss.offset);
END;
END;

largestDepthCount: FileLists.InclusionDepth ← 0;

IncreaseDepth: PROC [
root: FileLists.FE, minDepth: FileLists.InclusionDepth] =
BEGIN
includedFileDesc: FileLists.IncFileDesc;
includedFile: FileLists.FE;
IF feb[root].busy THEN
BEGIN
Ugly["\n "];
Ugly[ConvertUnsafe.ToRope[@feb[root].name]];
Ugly[" depends on a module that, in turn, depends on it\n"];
RETURN;
END;
IF feb[root].depth >= minDepth THEN RETURN;
feb[root].busy ← TRUE;
largestDepthCount ← MAX[minDepth, largestDepthCount];
feb[root].depth ← minDepth;
FOR i: FileLists.IncFile ← feb[root].includes, ifb[i].link UNTIL i = FileLists.IFnil DO
includedFileDesc ← ifb[i].includeFileDesc;
includedFile ← ifdb[includedFileDesc].file;
IncreaseDepth[includedFile, (minDepth + 1)];
ENDLOOP;
feb[root].busy ← FALSE;
END;


-- DISPLAY FILES WITH SOURCE BUT NO BCD ON DISK

CheckForSourceButNoBcd: PROC =
BEGIN
fe: FileLists.FE;
firstTime: BOOLEANTRUE;
anyWritten: BOOLEANFALSE;
numOnLine: CARDINAL ← 0;
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
FOR fe ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].source
AND feb[fe].stamp = FileLists.NullStamp -- no Bcd
AND ~(feb[fe].obsolete OR feb[fe].erroneous)
AND ~feb[fe].tableCompiled THEN
BEGIN
IF firstTime THEN
Ugly["\n\nSource files with no Bcds on the volume:\n"];
IF (numOnLine ← numOnLine + 1) > 4 THEN
{outStream.PutChar['\n]; numOnLine ← 1};
Ugly[" "];
Ugly[ConvertUnsafe.ToRope[@feb[fe].name]];
Ugly[(IF feb[fe].config THEN ".config" ELSE ".mesa")];
firstTime ← FALSE; anyWritten ← TRUE;
END;
ENDLOOP;
IF anyWritten THEN outStream.PutChar['\n];
END;


-- MARK FILES THAT DIRECTLY (THEMSELVES) NEED RECOMPILATION

badFilesExist: BOOLEANFALSE;

MarkDirectBads: PROC =
BEGIN
incFileDesc: FileLists.IncFileDesc;
incStamp, diskStamp: BcdDefs.VersionStamp;
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
ClearAllTags[]; -- prepare to tag files referenced in different versions
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
SELECT TRUE FROM
feb[fe].obsolete, feb[fe].erroneous => MarkFileBad[fe];
feb[fe].stamp # FileLists.NullStamp => -- Bcd is on disk
BEGIN
IF feb[fe].source AND feb[fe].sourceTime # FileLists.NullTime
AND feb[fe].bcdSourceTime # FileLists.NullTime
AND feb[fe].sourceTime # feb[fe].bcdSourceTime THEN MarkFileBad[fe]
ELSE
FOR i: FileLists.IncFile ← feb[fe].includes, ifb[i].link UNTIL i = FileLists.IFnil DO
incFileDesc ← ifb[i].includeFileDesc;
incStamp ← ifdb[incFileDesc].stamp;
diskStamp ← feb[ifdb[incFileDesc].file].stamp;
IF incStamp # diskStamp AND incStamp # FileLists.NullStamp
AND diskStamp # FileLists.NullStamp THEN
BEGIN
MarkFileBad[fe];
feb[ifdb[incFileDesc].file].tag ← TRUE; -- tag included file
EXIT;
END;
ENDLOOP;
END;
feb[fe].source => -- source but no bcd on disk
IF ~switches[noCompIfNotOnDisk] AND ~feb[fe].tableCompiled THEN
MarkFileBad[fe];
ENDCASE;
ENDLOOP;
PrintFilesInDifferentVersions[];
ClearAllTags[];
END;

MarkFileBad: PROC [fe: FileLists.FE] =
{feb[fe].bad ← badFilesExist ← TRUE};

ClearAllTags: PROC =
BEGIN
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
feb[fe].tag ← FALSE
ENDLOOP;
END;

PrintFilesInDifferentVersions: PROC =
BEGIN
firstTime: BOOLEANTRUE;
anyWritten: BOOLEANFALSE;
numOnLine: CARDINAL ← 0;

PrintFile: PROC [fe: FileLists.FE] =
BEGIN
IF firstTime THEN
Ugly["\nBcds included in a version different than that on the volume:\n"];
IF (numOnLine ← numOnLine + 1) > 4 THEN {outStream.PutChar['\n]; numOnLine ← 1};
Ugly[" "];
Ugly[ConvertUnsafe.ToRope[@feb[fe].name]];
Ugly[".bcd"];
firstTime ← FALSE; anyWritten ← TRUE;
END;

FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].tag THEN PrintFile[fe];
ENDLOOP;
IF anyWritten THEN outStream.PutChar['\n];
END;


-- OUTPUT COMPILATION ORDER, INCLUDE/INCLUDED BY RELATIONS, COMPILATION COMMAND

DoOutput: PROC =
BEGIN OPEN OutputDefs;
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
IF switches[order] THEN -- print compilation order
BEGIN
IF switches[multiple] THEN
BEGIN
s: STRING ← [FilenameChars + 1];
LongString.AppendChar[s, '.];
LongString.AppendString[s, outputFileName];
IF s[s.length] = '. THEN s.length ← s.length - 1;
OpenOutput["Source"L, s]
END
ELSE OpenOutput[outputFileName, ".list"L];
PrintCompileOrder[];
END;
IF switches[includes] THEN -- print includes/included by relations
BEGIN
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
IF switches[multiple] THEN
BEGIN
IF switches[order] THEN CloseOutput[];
OpenOutput[outputFileName, ".includes"L];
END
ELSE IF ~switches[order] THEN OpenOutput[outputFileName, ".list"L];
PrintIncludesRelation[];
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
IF switches[multiple] THEN
BEGIN
CloseOutput[];
OpenOutput[outputFileName, ".includedBy"L];
END
ELSE PutChar[FF];
PrintIncludedByRelation[];
CloseOutput[];
END
ELSE IF switches[order] THEN CloseOutput[];
IF userHitStop[inStream] THEN SIGNAL AbortChecking;
IF switches[consistent] THEN -- put compilation/binding command in Line.Cm
BEGIN
OpenOutput["Line"L, ".cm"L];
IF badFilesExist THEN {
ExtendBadMarks[]; -- mark bad all files depending on directly bad files
OutputConsistencyCmd[bind: FALSE];
PutCR[];
OutputConsistencyCmd[bind: TRUE]}
ELSE {
PutString["// The files appear consistent"L]; PutCR[];
outStream.PutChar['\n]; outStream.PutChar['\n];
Ugly["The files appear consistent\n"]};
CloseOutput[];
END;
END;


PrintCompileOrder: PROC =
BEGIN OPEN OutputDefs;
currentDepth, nextLargestDepth: FileLists.InclusionDepth;
anyConfigs: BOOLEANFALSE;

OutputFilesOfDepth: PROC [depth: FileLists.InclusionDepth, doConfigs: BOOLEAN] =
BEGIN
numOnLine: CARDINAL ← 0;
PutString[" "L];
nextLargestDepth ← 0;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].config THEN anyConfigs ← TRUE;
IF doConfigs # feb[fe].config THEN LOOP;
IF ~feb[fe].tag THEN
IF feb[fe].depth = depth THEN
BEGIN
IF (numOnLine ← numOnLine + 1) > 6 THEN
{PutCR[]; PutString[" "L]; numOnLine ← 1};
PutLongString[@feb[fe].name]; PutChar[SP];
feb[fe].tag ← TRUE;
END
ELSE nextLargestDepth ← MAX[feb[fe].depth, nextLargestDepth];
ENDLOOP;
PutCR[];
END;

PutString["Compilation Order (by inclusion depth):"L];
PutCR[];
ClearAllTags[];
currentDepth ← largestDepthCount;
WHILE currentDepth > 0 DO
OutputFilesOfDepth[depth: currentDepth, doConfigs: FALSE];
currentDepth ← nextLargestDepth;
ENDLOOP;
ClearAllTags[];
IF anyConfigs THEN
BEGIN
PutCR[]; PutCR[];
PutString["Binding Order (by inclusion depth):"L]; PutCR[];
currentDepth ← largestDepthCount;
WHILE currentDepth > 0 DO
OutputFilesOfDepth[depth: currentDepth, doConfigs: TRUE];
currentDepth ← nextLargestDepth;
ENDLOOP;
ClearAllTags[];
END;
PutCR[]; PutCR[];
END;


PrintIncludesRelation: PROC =
BEGIN OPEN OutputDefs;
sourceTimeOutput: BOOLEAN;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
sourceTimeOutput ← FALSE;
SELECT TRUE FROM
feb[fe].obsolete => PrintObsoleteFile[fe];
feb[fe].erroneous => PrintErroneousFile[fe];
feb[fe].stamp # FileLists.NullStamp => -- bcd is on the volume
BEGIN
PutFileName[fe, printStamp, 0];
IF feb[fe].bcdSourceTime # FileLists.NullStamp.time THEN
BEGIN
PutString[" (source: "L]; PutTime[feb[fe].bcdSourceTime]; PutChar[')];
sourceTimeOutput ← TRUE;
END;
IF feb[fe].source AND feb[fe].sourceTime # FileLists.NullStamp.time THEN
BEGIN
IF sourceTimeOutput THEN {PutCR[]; PutString[" "L]}
ELSE PutChar[SP];
IF feb[fe].bcdSourceTime # FileLists.NullStamp.time
AND feb[fe].sourceTime # feb[fe].bcdSourceTime THEN PutChar['*];
PutString["(source on volume: "L];
IF feb[fe].bcdSourceTime # FileLists.NullStamp.time
AND feb[fe].bcdSourceTime = feb[fe].sourceTime THEN PutString["[same]"L]
ELSE PutTime[feb[fe].sourceTime];
PutChar[')];
END;
PutString[" includes"L];
IF feb[fe].includes = FileLists.IFnil THEN PutString[" nothing"L]
ELSE PrintIncludedFiles[fe];
PutCR[]; PutCR[];
END;
ENDCASE;
ENDLOOP;
END;

PrintObsoleteFile: PROC [fe: FileLists.FE] =
BEGIN OPEN OutputDefs;
PutChar['*];
PutFileName[fe, noStamp, 0];
PutString[" was compiled by an obsolete version of the compiler"L];
PutCR[]; PutCR[];
END;

PrintErroneousFile: PROC [fe: FileLists.FE] =
BEGIN OPEN OutputDefs;
PutChar['*];
PutFileName[fe, noStamp, 0];
PutString[" was marked invalid because of compilation errors"L];
PutCR[]; PutCR[];
END;

PrintIncludedFiles: PROC [fe: FileLists.FE] =
BEGIN OPEN OutputDefs;
FOR i: FileLists.IncFile ← feb[fe].includes, ifb[i].link UNTIL i = FileLists.IFnil DO
BEGIN
incFileDesc: FileLists.IncFileDesc = ifb[i].includeFileDesc;
incStamp: BcdDefs.VersionStamp = ifdb[incFileDesc].stamp;
incFile: FileLists.FE = ifdb[incFileDesc].file;
incBad: BOOLEAN =
incStamp # feb[incFile].stamp AND feb[incFile].stamp # FileLists.NullStamp;
PutCR[];
PutChar[SP]; PutChar[IF incBad THEN '* ELSE SP];
PutFileName[incFile, printStamp, 0];
IF incBad THEN
{PutString[", but version included was ("L]; PutStamp[incStamp]; PutChar[')]}
ELSE IF incStamp = FileLists.NullStamp THEN PutString[" ** never referenced"L]
ELSE IF feb[incFile].stamp.time = FileLists.NullStamp.time THEN
{PutString[" [version included was "L]; PutStamp[incStamp]; PutString["]"L]};
END;
ENDLOOP;
END;

PutStamp: PROC [s: BcdDefs.VersionStamp] =
BEGIN OPEN OutputDefs;
PutTime[LOOPHOLE[s.time, Time.Packed]]; PutChar[SP];
PutNumber[s.net, [8, FALSE, FALSE, 1]]; PutChar['#];
PutNumber[s.host, [8, FALSE, FALSE, 1]]; PutChar['#];
END;

PutFileName: PROC [
fe: FileLists.FE, stampFlag: {printStamp, noStamp}, padLen: CARDINAL] =
BEGIN OPEN OutputDefs;
PutLongString[@feb[fe].name];
IF padLen # 0 THEN
BEGIN
IF feb[fe].name.length < padLen THEN
THROUGH [0..(padLen - feb[fe].name.length)) DO
PutChar[SP];
ENDLOOP
ELSE PutChar[SP];
END;
IF stampFlag = printStamp THEN
IF feb[fe].stamp.time # FileLists.NullStamp.time THEN
{PutString[" ("L]; PutStamp[feb[fe].stamp]; PutChar[')]};
END;


PrintIncludedByRelation: PROC =
BEGIN OPEN OutputDefs;
fe, incByFile: FileLists.FE;
i: FileLists.IncFile;
incFileDesc: FileLists.IncFileDesc;
lhs: BOOLEAN;
FOR fe ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].stamp # FileLists.NullStamp
OR (feb[fe].obsolete OR feb[fe].erroneous) THEN
BEGIN
PutFileName[fe, noStamp, 0];
PutString[" is included by"L];
IF feb[fe].includedBy = FileLists.IFnil THEN PutString[" nothing"L]
ELSE
BEGIN
lhs ← TRUE;
FOR i ← feb[fe].includedBy, ifb[i].link UNTIL i = FileLists.IFnil DO
incFileDesc ← ifb[i].includeFileDesc;
incByFile ← ifdb[incFileDesc].file;
IF lhs THEN
{PutCR[]; PutString[" "L]; PutFileName[incByFile, noStamp, 22]}
ELSE PutFileName[incByFile, noStamp, 0];
lhs ← ~lhs;
ENDLOOP;
END;
PutCR[]; PutCR[];
END;
ENDLOOP;
END;


nextLargestBadDepth: FileLists.InclusionDepth;
lastFileCompiledHadSwitch: BOOLEAN;

OutputConsistencyCmd: PROC [bind: BOOLEAN] =
BEGIN OPEN OutputDefs;
badDepth: FileLists.InclusionDepth;
putCommand: BOOLEANFALSE;
badDepth ← nextLargestBadDepth ← 0;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].bad AND feb[fe].config = bind THEN
{putCommand ← TRUE; badDepth ← MAX[feb[fe].depth, badDepth]};
ENDLOOP;
IF ~putCommand THEN RETURN;
ClearAllTags[];
PutString[IF bind THEN "Bind"L ELSE "Compile"L];
WHILE badDepth > 0 DO
CompileBadFilesOfDepth[badDepth, bind];
IF switches[pause] AND nextLargestBadDepth > 0 THEN
PutString[IF lastFileCompiledHadSwitch THEN "p"L ELSE "/p"L];
badDepth ← nextLargestBadDepth;
ENDLOOP;
ClearAllTags[];
PutCR[]; PutCR[];
IF ~bind THEN ListNeededFiles[];
END;

ExtendBadMarks: PROC =
BEGIN

MarkBad: PROC [fe: FileLists.FE] =
BEGIN
incFileDesc: FileLists.IncFileDesc;
IF feb[fe].tag THEN RETURN; -- fe & includers already marked bad
IF CheckCycle[fe] THEN RETURN;
feb[fe].busy ← TRUE;
FOR i: FileLists.IncFile ← feb[fe].includedBy, ifb[i].link UNTIL i = FileLists.IFnil DO
incFileDesc ← ifb[i].includeFileDesc;
MarkBad[ifdb[incFileDesc].file];
ENDLOOP;
feb[fe].bad ← TRUE;
feb[fe].tag ← TRUE;
feb[fe].busy ← FALSE;
END;

ClearAllTags[];
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].bad THEN MarkBad[fe];
ENDLOOP;
ClearAllTags[];
END;

CheckCycle: PROC [fe: FileLists.FE] RETURNS [BOOLEAN] =
{RETURN[feb[fe].busy]};

CompileBadFilesOfDepth: PROC [depth: FileLists.InclusionDepth, bind: BOOLEAN] =
BEGIN OPEN OutputDefs;
nextLargestBadDepth ← 0;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].config # bind THEN LOOP;
IF feb[fe].bad AND ~feb[fe].tag THEN
IF feb[fe].depth = depth THEN
BEGIN
lastFileCompiledHadSwitch ← FALSE;
PutChar[SP]; PutLongString[@feb[fe].name];
IF ~bind AND (~feb[fe].crossJumped OR ~feb[fe].dStar OR feb[fe].longAlto) THEN
BEGIN
PutChar['/];
IF ~feb[fe].crossJumped THEN PutString["-j"L];
IF ~feb[fe].dStar THEN PutChar['a];
IF feb[fe].longAlto THEN PutChar['l];
lastFileCompiledHadSwitch ← TRUE;
END;
feb[fe].tag ← TRUE;
END
ELSE nextLargestBadDepth ← MAX[feb[fe].depth, nextLargestBadDepth];
ENDLOOP;
END;

ListNeededFiles: PROC =
BEGIN
TagNeededFilesNotOnDisk[];
ListNeededSourceFiles[];
ListNeededBcdFiles[];
END;

TagNeededFilesNotOnDisk: PROC =
BEGIN
fe: FileLists.FE;

ConditionallyRemoveTag: PROC [fe: FileLists.FE] =
BEGIN
bcdName: STRING ← [FilenameChars + 1];
sourceName: STRING ← [FilenameChars + 1];
fileCap: File.Capability ← File.nullCapability;
IF ~feb[fe].bad THEN -- look for Bcd
BEGIN
LongString.AppendString[to: bcdName, from: @feb[fe].name];
LongString.AppendString[to: bcdName, from: ".bcd"L];
fileCap ← Directory.Lookup[fileName: bcdName, permissions: Directory.ignore
! Directory.Error => CONTINUE];
IF fileCap # File.nullCapability THEN {feb[fe].tag ← FALSE; RETURN};
END
ELSE
BEGIN
LongString.AppendString[to: sourceName, from: @feb[fe].name];
LongString.AppendString[to: sourceName, from: ".mesa"L];
fileCap ← Directory.Lookup[fileName: sourceName, permissions: Directory.ignore
! Directory.Error => CONTINUE];
IF fileCap # File.nullCapability THEN {feb[fe].tag ← FALSE; RETURN};
sourceName.length ← 0;
LongString.AppendString[to: sourceName, from: @feb[fe].name];
LongString.AppendString[to: sourceName, from: ".config"L];
fileCap ← Directory.Lookup[fileName: sourceName, permissions: Directory.ignore
! Directory.Error => CONTINUE];
IF fileCap # File.nullCapability THEN {feb[fe].tag ← FALSE; RETURN};
END;
END;

ClearAllTags[];
-- tag bad files and those they need (include)
FOR fe ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].bad THEN
BEGIN
feb[fe].tag ← TRUE;
FOR i: FileLists.IncFile ← feb[fe].includes, ifb[i].link UNTIL i = IFnil DO
IF ifb[i].fileOpenedByCompiler THEN
BEGIN
incFileDesc: FileLists.IncFileDesc = ifb[i].includeFileDesc;
incFile: FileLists.FE = ifdb[incFileDesc].file;
feb[incFile].tag ← TRUE;
END;
ENDLOOP;
END;
ENDLOOP;
-- Remove tags on files when a good bcd or a source exists.
FOR fe ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].tag THEN ConditionallyRemoveTag[fe];
ENDLOOP;
END;

ListNeededSourceFiles: PROC =
BEGIN
firstTime: BOOLEANTRUE;
anyWritten: BOOLEANFALSE;
numOnLine: CARDINAL ← 0;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF ~feb[fe].source AND (feb[fe].tag AND feb[fe].bad) THEN
BEGIN -- by def. of tag, source not on volume
IF firstTime THEN
BEGIN
Ugly["\nSource files needed but not on the volume:\n "];
firstTime ← FALSE;
END;
IF (numOnLine ← numOnLine + 1) > 4 THEN
{Ugly["\n "];
numOnLine ← 1};
Ugly[ConvertUnsafe.ToRope[@feb[fe].name]];
Ugly[(IF feb[fe].config THEN ".config " ELSE ".mesa ")];
anyWritten ← TRUE;
feb[fe].tag ← FALSE;
END;
ENDLOOP;
IF anyWritten THEN outStream.PutChar['\n];
END;

ListNeededBcdFiles: PROC =
BEGIN
firstTime: BOOLEANTRUE;
anyWritten: BOOLEANFALSE;
numOnLine: CARDINAL ← 0;
FOR fe: FileLists.FE ← FileLists.fileList, feb[fe].link UNTIL fe = FileLists.FEnil DO
IF feb[fe].tag --AND NOT feb[fe].bad-- THEN
BEGIN -- by def. of tag, bcd not on disk
IF firstTime THEN
BEGIN
Ugly["\nBcds needed but not on the volume:\n "];
firstTime ← FALSE;
END;
IF (numOnLine ← numOnLine + 1) > 4 THEN
{Ugly["\n "];
numOnLine ← 1};
Ugly[ConvertUnsafe.ToRope[@feb[fe].name]];
Ugly[".bcd "];
anyWritten ← TRUE;
feb[fe].tag ← FALSE;
END;
ENDLOOP;
IF anyWritten THEN outStream.PutChar['\n];
END;
userHitStop: PROC[in: IO.Handle] RETURNS[abort: BOOL] = {
abort ← in.UserAbort[];
IF abort THEN in.ResetUserAbort[];
};
-- MAIN BODY CODE
Commander.Register[
"IncludeChecker",
CheckFiles,
"..for determining correct compilation order after changes are made"
];
END.