--waterlily.mesaKaren Kolling January 8, 1981 12:05 PM
DIRECTORY
DisplayDefs: FROM "DisplayDefs"
USING[DisplayOff, DisplayOn, SetSystemDisplaySize],
ImageDefs: FROM "ImageDefs"
USING[MakeImage, StopMesa],
IODefs: FROM "IODefs"
USING[ControlA, ControlH, ControlQ, ControlR, ControlW, ControlX, ControlZ, CR, NumberFormat, ReadEditedString, SP, SetInputStream, SetOutputStream, TAB, WriteChar, WriteLine, WriteNumber, WriteString],
SegmentDefs: FROM "SegmentDefs"
USING[Append, FileNameError, Read, Write],
StreamDefs: FROM "StreamDefs"
USING[CursorTrack, DiskHandle, DisplayHandle, GetDefaultDisplayStream, GetDefaultKey, KeyboardHandle, NewByteStream, StreamError, StreamHandle],
StringDefs: FROM "StringDefs"
USING[AppendChar, AppendString, AppendSubString, StringBoundsFault, StringToNumber, SubStringDescriptor],
SystemDefs: FROM "SystemDefs"
USING [AllocateHeapNode, AllocateHeapString, AllocateSegment, FreeHeapNode, FreeHeapString, FreeSegment],
TimeDefs: FROM "TimeDefs"
USING [AppendFullDayTime, DefaultTime, UnpackDT];
waterlily: PROGRAM IMPORTS DisplayDefs, ImageDefs, IODefs, SegmentDefs, StreamDefs, StringDefs, SystemDefs, TimeDefs =
BEGIN
--part of this algorithm cribbed from Heckel, Paul. A Technique For Isolating Differences Between Files. Comm. ACM 21,4 (April 1978), 264-268. Pointer to which courtesy of Ed Taft.
Alarm: PROCEDURE = BEGIN AlarmCalled END;
AlarmCalled: SIGNAL = CODE;
symbolTable: ARRAY [0..27) OF SymbolTableKey ← ALL[NIL]; --a thru z plus one other.
SymbolTableKey: TYPE = POINTER TO SymbolTableEntry;
SymbolTableEntry: TYPE = RECORD[left, right: SymbolTableKey, timesInOldFile, timesInNewFile: INTEGER, lineNumberInOldFile, lineNumberInNewFile: INTEGER, text: STRING];
SymbolTableProcedure: TYPE = PROCEDURE[pntr: SymbolTableKey];
duplicateList: POINTER TO DuplicateRecord ← NIL;
DuplicateRecord: TYPE = RECORD[lineNum: INTEGER, symPntr: SymbolTableKey, nextDupRec: POINTER TO DuplicateRecord];
oldArray: DESCRIPTOR FOR ARRAY [0..0) OF FileArrayEntry;
newArray: DESCRIPTOR FOR ARRAY [0..0) OF FileArrayEntry;
FileArrayEntry: TYPE = RECORD[symTablePntr: SymbolTableKey, lineNumInOtherFile: INTEGER, typeOfPntr: {symTable, lineNum}];
newArrayPntr, oldArrayPntr: POINTER;
totalLinesInNewFile: INTEGER ← 0;
totalLinesInOldFile: INTEGER ← 0;
newFile: StreamDefs.DiskHandle;
oldFile: StreamDefs.DiskHandle;
difFile: StreamDefs.DiskHandle;
newFileName: STRING;
oldFileName: STRING;
difFileName: STRING;
display: StreamDefs.DisplayHandle;
keyboard: StreamDefs.KeyboardHandle;
Asterisks: STRING = "**************************************";
--these switches are global or local.
switchBravoNewFile, switchBravoOldFile: BOOLEAN ← TRUE;
switchIgnoreEmptyLinesNewFile, switchIgnoreEmptyLinesOldFile: BOOLEAN ← TRUE;
--these switches are global only.
switchLinesForMatch: INTEGER ← 3;
switchLinesForContext: INTEGER ← 1;
switchPause: BOOLEAN ← TRUE;
anyDifferencesSeen: BOOLEAN ← FALSE;
indexN, indexO: INTEGER;
SetSymbolTableEntry: PROCEDURE [nextLine: STRING, newOrOld: {new, old}, lineNumCurrentFile: INTEGER, leadingChar: CHARACTER] =
BEGIN
index: CARDINAL ← (IF leadingChar IN [’A..’Z] THEN (leadingChar - ’A) ELSE IF leadingChar IN [’a..’z] THEN (leadingChar - ’a) ELSE 26);
pntr: SymbolTableKey ← symbolTable[index];
oldPntr: POINTER TO SymbolTableKey ← @symbolTable[index];
DO
IF pntr = NIL
THEN GOTO notfound
ELSE
SELECT CompareStrings[nextLine, pntr.text] FROM
greaterthan => oldPntr ← @pntr.right;
equalto => GOTO found;
lessthan => oldPntr ← @pntr.left;
ENDCASE => Alarm[];
pntr ← oldPntr↑;
REPEAT
notfound => BEGIN
pntr ← SystemDefs.AllocateHeapNode[SIZE[SymbolTableEntry]];
pntr↑ ← [left: NIL, right: NIL, timesInOldFile: (IF newOrOld = old THEN 1 ELSE 0), timesInNewFile: (IF newOrOld = new THEN 1 ELSE 0), lineNumberInOldFile: (IF newOrOld = old THEN lineNumCurrentFile ELSE 0), lineNumberInNewFile: (IF newOrOld = new THEN lineNumCurrentFile ELSE 0), text: ];
pntr.text ← SystemDefs.AllocateHeapString[nextLine.length];
StringDefs.AppendString[pntr.text, nextLine];
oldPntr↑ ← pntr;
END;
found => BEGIN
SELECT newOrOld FROM
new => BEGIN
IF pntr.timesInNewFile = 0
THEN pntr.lineNumberInNewFile ← lineNumCurrentFile
ELSE EnterDuplicateRecord[pntr, lineNumCurrentFile];
IF pntr.timesInNewFile < 2 THEN pntr.timesInNewFile ← pntr.timesInNewFile + 1;
END;
old => BEGIN
IF pntr.timesInOldFile = 0
THEN pntr.lineNumberInOldFile ← lineNumCurrentFile
ELSE EnterDuplicateRecord[pntr, lineNumCurrentFile];
IF pntr.timesInOldFile < 2 THEN pntr.timesInOldFile ← pntr.timesInOldFile + 1;
END;
ENDCASE => Alarm[];
END;
ENDLOOP;
END;
EnterDuplicateRecord: PROCEDURE [pntr: SymbolTableKey, lineNumCurrentFile: INTEGER] =
BEGIN
tempDupRec: POINTER TO DuplicateRecord ← SystemDefs.AllocateHeapNode[SIZE[DuplicateRecord]];
tempDupRec.nextDupRec ← duplicateList;
duplicateList ← tempDupRec;
duplicateList.lineNum ← lineNumCurrentFile;
duplicateList.symPntr ← pntr;
END;
ScanSymbolTable: PROCEDURE [Test: SymbolTableProcedure] =
BEGIN
index: CARDINAL;
ProcessEntry: PROCEDURE[pntr: SymbolTableKey, Test: SymbolTableProcedure] =
BEGIN
IF pntr = NIL THEN RETURN;
Test[pntr];
ProcessEntry[pntr.right, Test];
ProcessEntry[pntr.left, Test];
END;
FOR index IN [0..27) DO ProcessEntry[symbolTable[index], Test]; ENDLOOP;
END;
CompareStrings: PROCEDURE [firstString, secondString: STRING] RETURNS [{greaterthan, equalto, lessthan}] =
BEGIN
index: CARDINAL ← 0;
UNTIL ((index >= firstString.length) OR (index >= secondString.length))
DO
IF firstString[index] > secondString[index] THEN RETURN[greaterthan];
IF firstString[index] < secondString[index] THEN RETURN[lessthan];
index ← index + 1;
ENDLOOP;
IF firstString.length = secondString.length THEN RETURN[equalto];
IF firstString.length > secondString.length THEN RETURN[greaterthan];
RETURN[lessthan];
END;
--this routine has two functions: (1) to get the leading non-sp, non-tab character, and (2) to determine if the line should be skipped.
GetLeadingNonSemiBlankCharacter: PROCEDURE [line: STRING, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [leadingChar: CHARACTER, skipLine: BOOLEAN] =
BEGIN
index: CARDINAL ← 0;
WHILE index < line.length
DO
IF ((line[index] # IODefs.SP) AND (line[index] # IODefs.TAB)) THEN GOTO found;
index ← index + 1;
REPEAT found => RETURN[leadingChar: line[index], skipLine: FALSE];
ENDLOOP;
--here on an "empty" line.
RETURN[leadingChar: IODefs.SP, skipLine: localSwitchIgnoreEmpties];
END;
eof: SIGNAL = CODE;
ReadLineFromFile: PROCEDURE [InputFile: StreamDefs.DiskHandle, line: STRING, localSwitchBravo, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [char: CHARACTER] =
BEGIN
SignalEof: PROCEDURE = BEGIN eof END;
eofSeen: BOOLEAN ← FALSE;
skipLine: BOOLEAN;
DO
line.length ← 0;
DO
IF InputFile.endof[InputFile] THEN GOTO saweof;
line[line.length] ← InputFile.get[InputFile];
IF (localSwitchBravo AND (line[line.length] = IODefs.ControlZ))
THEN
DO
IF InputFile.endof[InputFile] THEN GOTO saweof;
IF InputFile.get[InputFile] = IODefs.CR THEN GOTO eoline;
ENDLOOP;
IF line[line.length] = IODefs.CR THEN EXIT;
line.length ← line.length + 1;
REPEAT
saweof => IF line.length = 0 THEN SignalEof[] ELSE eofSeen ← TRUE;
eoline => NULL;
ENDLOOP;
[char, skipLine] ← GetLeadingNonSemiBlankCharacter[line, localSwitchIgnoreEmpties];
IF NOT skipLine THEN EXIT;
IF eofSeen THEN SignalEof[];
ENDLOOP;
END;
LegalCharInFilename: PROCEDURE[char: CHARACTER] RETURNS[BOOLEAN] =
BEGIN
SELECT char FROM
IN [’A..’Z], IN [’a..’z], IN [’0..’9], ’+, ’-, ’., ’!, ’$ => RETURN[TRUE];
ENDCASE => RETURN[FALSE];
END;
SkipSpacesAndTabs: PROCEDURE[char: CHARACTER, InputStream: StreamDefs.StreamHandle] RETURNS[nextChar: CHARACTER] =
BEGIN
nextChar ← char;
WHILE ((nextChar = IODefs.SP) OR (nextChar = IODefs.TAB))
DO nextChar ← InputStream.get[InputStream]; ENDLOOP;
END;
TerminalHoHoDisplayOutput: PROCEDURE[string: STRING, pause: BOOLEAN] =
BEGIN
IODefs.WriteChar[IODefs.CR];
IODefs.WriteLine[string];
IF pause
THEN
BEGIN
IODefs.WriteString["TYPE ANY CHARACTER TO EXIT: "L];
IODefs.SetInputStream[keyboard];
[] ← keyboard.get[keyboard];
END;
ImageDefs.StopMesa[];
END;
OpenDifFileAndWriteHeader: PROCEDURE =
BEGIN
dayTime: STRING ← [24];
subStr: StringDefs.SubStringDescriptor;
index: CARDINAL ← 0;
DO
IF ((index >= oldFileName.length) OR (oldFileName[index] = ’.)) THEN EXIT;
index ← index + 1;
ENDLOOP;
difFileName ← SystemDefs.AllocateHeapString[index + 4];
subStr ← [oldFileName, 0, index];
StringDefs.AppendSubString[difFileName, @subStr];
StringDefs.AppendString[difFileName, ".dif"];
difFile ← StreamDefs.NewByteStream[difFileName, SegmentDefs.Write + SegmentDefs.Append];
IODefs.SetOutputStream[difFile];
TimeDefs.AppendFullDayTime[dayTime, TimeDefs.UnpackDT[TimeDefs.DefaultTime]]; --current time.
IODefs.WriteString["
Waterlily Version 1.6 "]; IODefs.WriteLine[dayTime];
IODefs.WriteString["

Differences between files "]; IODefs.WriteString[oldFileName]; IODefs.WriteString[" and "]; IODefs.WriteString[newFileName]; IODefs.WriteLine[":"]; IODefs.WriteChar[IODefs.CR];
END;
FinishUp: PROCEDURE[openDifFile, writeAndCloseDifFile: BOOLEAN, difMsg: STRING, displayMsg: STRING, pause: BOOLEAN] =
BEGIN
IF openDifFile THEN OpenDifFileAndWriteHeader[];
IF writeAndCloseDifFile
THEN
BEGIN
IODefs.WriteLine[difMsg];
difFile.destroy[difFile];
END;
DisplayDefs.DisplayOn[];
IODefs.SetOutputStream[display];
TerminalHoHoDisplayOutput[displayMsg, pause];
END;
TotalNStepsCursor: CARDINAL ← 9;
CenterX: CARDINAL ← (605/2) - 8;
YOffset: CARDINAL ← 20; -- must be >= 16.
CursorYInitial: CARDINAL ← 807 - YOffset;
CursorYFinal: CARDINAL ← YOffset - 16;
CursorYInc: CARDINAL ← (CursorYInitial - CursorYFinal)/TotalNStepsCursor;
currentStepCursor: CARDINAL ← 0;
cursorX: POINTER TO CARDINAL = LOOPHOLE[426B];
cursorY: POINTER TO CARDINAL = LOOPHOLE[427B];
InitializeCursor: PROCEDURE =
BEGIN
cursorPntrs: ARRAY [0..16) OF POINTER TO CARDINAL = [LOOPHOLE[431B], LOOPHOLE[432B], LOOPHOLE[433B], LOOPHOLE[434B], LOOPHOLE[435B], LOOPHOLE[436B], LOOPHOLE[437B], LOOPHOLE[440B], LOOPHOLE[441B], LOOPHOLE[442B], LOOPHOLE[443B], LOOPHOLE[444B], LOOPHOLE[445B], LOOPHOLE[446B], LOOPHOLE[447B], LOOPHOLE[450B]];
cursorValues: ARRAY [0..16) OF CARDINAL = [105757B, 104211B, 104211B, 104217B, 124214B, 124212B, 174211B, 0B, 102042B, 102042B, 102024B, 102024B, 102010B, 102010B, 173610B, 0B];
index: CARDINAL;
StreamDefs.CursorTrack[FALSE];
cursorX↑ ← CenterX;
cursorY↑ ← CursorYInitial;
FOR index IN [0..16)
DO
cursorPntrs[index]↑ ← cursorValues[index];
ENDLOOP;
END;
StepCursor: PROCEDURE =
BEGIN
currentStepCursor ← currentStepCursor + 1;
IF currentStepCursor > TotalNStepsCursor THEN Alarm[];
cursorY↑ ← cursorY↑ - CursorYInc;
END;
ReadComDotCmOrPrompt: PROCEDURE =
BEGIN
comDotCm: StreamDefs.DiskHandle ← StreamDefs.NewByteStream["com.cm"L, SegmentDefs.Read];
newFileSeen, oldFileSeen: BOOLEAN ← FALSE;
SignalSyntaxError: PROCEDURE = BEGIN SyntaxError END;
SyntaxError: SIGNAL = CODE;
ProcessGlobalSwitches: PROCEDURE[char: CHARACTER] RETURNS[nextChar: CHARACTER] =
BEGIN
DO
IF char = ’/ THEN char ← comDotCm.get[comDotCm];
SELECT char FROM
’H, ’h => BEGIN
DisplayDefs.SetSystemDisplaySize[nTextLines: 30, nPages: 25]; -- 25 is a guess that seems to work.
TerminalHoHoDisplayOutput["This is the format of the command line:

Waterlily <globalswitches> filename1<localswitches> filename2<localswitches>

where the global switches acceptable are:
b bravo format files. everything between ↑Z and CR is ignored. default = TRUE.
i ignore blank and empty lines. default = TRUE.
p on empty file or no difs encountered, pause instead of writing dif file. default = TRUE.
/#m number of lines to consider a match. default = 3.
/#c number of trailing lines to output for context. default = 1.

The local switches acceptable are b and i.

Examples of command lines are:
Waterlily file1 file2
Waterlily /-b foo1 foo2
Waterlily /6m foo1/-b foo2/-b-i
Waterlily (this form will prompt for the filenames.)

The differences found are written on the file filename1.dif."L, TRUE];
END;
’b, ’B => BEGIN
switchBravoNewFile ← TRUE; switchBravoOldFile ← TRUE;
END;
’p, ’P => BEGIN switchPause ← TRUE; END;
’i, ’I => BEGIN
switchIgnoreEmptyLinesNewFile ← TRUE; switchIgnoreEmptyLinesOldFile ← TRUE;
END;
’- => BEGIN
char ← comDotCm.get[comDotCm];
SELECT char FROM
’b, ’B => BEGIN
switchBravoNewFile ← FALSE; switchBravoOldFile ← FALSE;
END;
’i, ’I => BEGIN
switchIgnoreEmptyLinesNewFile ← FALSE; switchIgnoreEmptyLinesOldFile ← FALSE;
END;
’p, ’P => BEGIN switchPause ← FALSE; END;
ENDCASE => SignalSyntaxError[];
END;
IN [’0..’9] => BEGIN
number: CARDINAL;
numberString: STRING ← [5];
WHILE char IN [’0..’9]
DO
IF numberString.length > 5 THEN SignalSyntaxError[];
numberString[numberString.length] ← char;
numberString.length ← numberString.length + 1;
char ← comDotCm.get[comDotCm];
ENDLOOP;
number ← StringDefs.StringToNumber[numberString, 10 ! ANY => SignalSyntaxError[]];
SELECT char FROM
’m, ’M => BEGIN
IF number <1 THEN Alarm[];
switchLinesForMatch ← number;
END;
’c, ’C => switchLinesForContext ← number;
ENDCASE => SignalSyntaxError[];
END;
ENDCASE => SignalSyntaxError[];
char ← comDotCm.get[comDotCm];
SELECT char FROM
’-, ’b, ’B, ’h, ’H, ’i, ’I, ’p, ’P, ’/, IN [’0..’9] => LOOP;
IODefs.SP, IODefs.TAB => BEGIN
char ← SkipSpacesAndTabs[char, comDotCm];
IF (char = ’/)
THEN LOOP
ELSE RETURN[char];
END;
ENDCASE => RETURN[char];
ENDLOOP;
END;
ProcessLocalSwitches: PROCEDURE [switchBravo, switchIgnore: POINTER TO BOOLEAN] RETURNS[nextChar: CHARACTER] =
BEGIN
nextChar ← comDotCm.get[comDotCm];
DO
SELECT nextChar FROM
’b, ’B => switchBravo↑ ← TRUE;
’i, ’I => switchIgnore↑ ← TRUE;
’- => BEGIN
nextChar ← comDotCm.get[comDotCm];
SELECT nextChar FROM
’b, ’B => switchBravo↑ ← FALSE;
’i, ’I => switchIgnore↑ ← FALSE;
ENDCASE => SignalSyntaxError[];
END;
ENDCASE => SignalSyntaxError[];
nextChar ← comDotCm.get[comDotCm];
SELECT nextChar FROM
’/ => nextChar ← comDotCm.get[comDotCm];
’-, ’b, ’B, ’i, ’I => NULL;
ENDCASE => RETURN[nextChar];
ENDLOOP;
END;
FileNotAccessible: SIGNAL = CODE;
OpenFile: PROCEDURE[fileSeen: POINTER TO BOOLEAN, tempFileName: STRING, fileName: POINTER TO STRING, file: POINTER TO StreamDefs.DiskHandle] =
BEGIN
SignalFileNotAccessible: PROCEDURE = BEGIN FileNotAccessible END;
file↑ ← StreamDefs.NewByteStream[tempFileName, SegmentDefs.Read ! SegmentDefs.FileNameError => SignalFileNotAccessible[]];
fileName↑ ← SystemDefs.AllocateHeapString[tempFileName.length];
StringDefs.AppendString[fileName↑, tempFileName];
fileSeen↑ ← TRUE;
END;
ProcessFilename: PROCEDURE[char: CHARACTER] RETURNS[nextChar: CHARACTER] =
BEGIN
tempFileName: STRING ← [100];
IF newFileSeen AND oldFileSeen THEN SignalSyntaxError[];
--guaranteed at least one legal char on entry.
nextChar ← char;
WHILE LegalCharInFilename[nextChar]
DO
StringDefs.AppendChar[tempFileName, nextChar];
nextChar ← comDotCm.get[comDotCm];
ENDLOOP;
IF NOT oldFileSeen
THEN
BEGIN
OpenFile[@oldFileSeen, tempFileName, @oldFileName, @oldFile];
IF nextChar = ’/ THEN nextChar ← ProcessLocalSwitches[@switchBravoOldFile, @switchIgnoreEmptyLinesOldFile];
END
ELSE
BEGIN
OpenFile[@newFileSeen, tempFileName, @newFileName, @newFile];
IF nextChar = ’/ THEN nextChar ← ProcessLocalSwitches[@switchBravoNewFile, @switchIgnoreEmptyLinesNewFile];
END;
nextChar ← SkipSpacesAndTabs[nextChar, comDotCm];
END;
Prompt: PROCEDURE =
BEGIN
PromptForFile: PROCEDURE[typeOfFile: STRING, fileSeen: POINTER TO BOOLEAN, fileName: POINTER TO STRING, file: POINTER TO StreamDefs.DiskHandle, switchBravo, switchIgnore: POINTER TO BOOLEAN] =
BEGIN
tempFileName: STRING ← [100];
nextChar: CHARACTER;
EditedCharCheck: PROCEDURE[char: CHARACTER] RETURNS[BOOLEAN] =
BEGIN
RETURN[NOT ((LegalCharInFilename[char]) OR (char = IODefs.ControlH) OR (char = IODefs.ControlA) OR (char = IODefs.ControlW) OR (char = IODefs.ControlQ) OR (char = IODefs.ControlX) OR (char = IODefs.ControlR))];
END;
IODefs.SetInputStream[keyboard];
DO
BEGIN
ENABLE
BEGIN
StringDefs.StringBoundsFault => GOTO err;
StreamDefs.StreamError => IF error = StreamAccess THEN GOTO err;
END;
IODefs.WriteString["
"L]; IODefs.WriteString[typeOfFile]; IODefs.WriteString[" file: "L];
tempFileName.length ← 0;
nextChar ← IODefs.ReadEditedString[tempFileName, EditedCharCheck, TRUE];
IF ((tempFileName.length = 0) OR (nextChar # IODefs.CR)) THEN GOTO err;
OpenFile[fileSeen, tempFileName, fileName, file! FileNotAccessible => GOTO err];
EXIT;
EXITS err => IODefs.WriteString["
File not found or syntax error"L];
END;
ENDLOOP;
END;
IF NOT oldFileSeen
THEN PromptForFile["Old"L, @oldFileSeen, @oldFileName, @oldFile, @switchBravoOldFile, @switchIgnoreEmptyLinesOldFile];
IF NOT newFileSeen
THEN PromptForFile["New"L, @newFileSeen, @newFileName, @newFile, @switchBravoNewFile, @switchIgnoreEmptyLinesNewFile];
IODefs.WriteString["
"L];
END;
BEGIN
ENABLE
BEGIN
StreamDefs.StreamError => IF error = StreamAccess THEN TerminalHoHoDisplayOutput["Syntax error.", TRUE];
FileNotAccessible => TerminalHoHoDisplayOutput["File not found.", TRUE];
SyntaxError => TerminalHoHoDisplayOutput["Syntax error.", TRUE];
END;
char: CHARACTER ← IODefs.SP;
char ← SkipSpacesAndTabs[char, comDotCm];
WHILE LegalCharInFilename[char]
DO char ← comDotCm.get[comDotCm]; ENDLOOP; --skip past some form of "Waterlily".
char ← SkipSpacesAndTabs[char, comDotCm];
IF (char = ’/) THEN char ← ProcessGlobalSwitches[char];
IF LegalCharInFilename[char] THEN char ← ProcessFilename[char];
IF LegalCharInFilename[char] THEN char ← ProcessFilename[char];
char ← SkipSpacesAndTabs[char, comDotCm];
IF ((char # IODefs.CR) AND (char # ’;)) THEN SignalSyntaxError[];
comDotCm.destroy[comDotCm];
END;
IF NOT (newFileSeen AND oldFileSeen) THEN Prompt[]; --does its own error checking since it can recover.
END;
-- main body of module.
ImageDefs.MakeImage["Waterlily.image"];
display ← StreamDefs.GetDefaultDisplayStream[];
keyboard ← StreamDefs.GetDefaultKey[];
IODefs.SetOutputStream[display]; --we don’t stream out to anything but the display until writing into the dif file.
display.put[display, IODefs.CR]; -- avoid mesa bug in typescript handling involving image files.
ReadComDotCmOrPrompt[];
DisplayDefs.DisplayOff[black];
InitializeCursor[];
BEGIN
ScanDuplicateList: PROCEDURE[array: DESCRIPTOR FOR ARRAY [0..0) OF FileArrayEntry] =
BEGIN
tempDuplicateList: POINTER TO DuplicateRecord;
UNTIL duplicateList = NIL
DO
array[duplicateList.lineNum] ← [symTablePntr: duplicateList.symPntr, lineNumInOtherFile: 0, typeOfPntr: symTable];
tempDuplicateList ← duplicateList;
duplicateList ← duplicateList.nextDupRec;
SystemDefs.FreeHeapNode[tempDuplicateList];
ENDLOOP;
END;
--Pass 1.
nextLine: STRING ← SystemDefs.AllocateHeapString[1500];
leadingChar: CHARACTER;
DO
leadingChar ← ReadLineFromFile[newFile, nextLine, switchBravoNewFile, switchIgnoreEmptyLinesNewFile ! eof => GOTO donewithfile];
SetSymbolTableEntry[nextLine, new, totalLinesInNewFile, leadingChar];
totalLinesInNewFile ← totalLinesInNewFile + 1;
REPEAT donewithfile => IF totalLinesInNewFile = 0 THEN GOTO emptyfile;
ENDLOOP;
StepCursor[];
--Pass 1 and 1/2.
newArrayPntr ← SystemDefs.AllocateSegment[totalLinesInNewFile*SIZE[FileArrayEntry]];
newArray ← DESCRIPTOR[newArrayPntr, totalLinesInNewFile];
BEGIN
Test: SymbolTableProcedure =
BEGIN
IF (pntr.timesInNewFile >= 1) THEN newArray[pntr.lineNumberInNewFile] ← [symTablePntr: pntr, lineNumInOtherFile: 0, typeOfPntr: symTable];
END;
ScanSymbolTable[Test];
END;
ScanDuplicateList[newArray];
StepCursor[];
--Pass 2.
DO
leadingChar ← ReadLineFromFile[oldFile, nextLine, switchBravoOldFile, switchIgnoreEmptyLinesOldFile ! eof => GOTO donewithfile];
SetSymbolTableEntry[nextLine, old, totalLinesInOldFile, leadingChar];
totalLinesInOldFile ← totalLinesInOldFile + 1;
REPEAT donewithfile => IF totalLinesInOldFile = 0 THEN GOTO emptyfile;
ENDLOOP;
SystemDefs.FreeHeapString[nextLine];
StepCursor[];
--Pass 2 and 1/2.
oldArrayPntr ← SystemDefs.AllocateSegment[totalLinesInOldFile*SIZE[FileArrayEntry]];
oldArray ← DESCRIPTOR[oldArrayPntr, totalLinesInOldFile];
BEGIN
Test: SymbolTableProcedure =
BEGIN
IF (pntr.timesInOldFile >= 1) THEN oldArray[pntr.lineNumberInOldFile] ← [symTablePntr: pntr, lineNumInOtherFile: 0, typeOfPntr: symTable];
END;
ScanSymbolTable[Test];
END;
ScanDuplicateList[oldArray];
StepCursor[];
EXITS emptyfile => FinishUp[openDifFile: (NOT switchPause), writeAndCloseDifFile: (NOT switchPause), difMsg: "At least one of these files is effectively empty.", displayMsg: "At least one of these files is effectively empty.", pause: switchPause];
END;
--Pass 3.
BEGIN
Test: SymbolTableProcedure =
BEGIN
IF ((pntr.timesInOldFile = 1) AND (pntr.timesInNewFile = 1))
THEN
BEGIN
newArray[pntr.lineNumberInNewFile].typeOfPntr ← lineNum;
newArray[pntr.lineNumberInNewFile].lineNumInOtherFile ← pntr.lineNumberInOldFile;
oldArray[pntr.lineNumberInOldFile].typeOfPntr ← lineNum;
oldArray[pntr.lineNumberInOldFile].lineNumInOtherFile ← pntr.lineNumberInNewFile;
END;
END;
ScanSymbolTable[Test];
END;
StepCursor[];
--Pass 4.
indexN ← 0;
WHILE indexN < totalLinesInNewFile
DO
IF (newArray[indexN].typeOfPntr = lineNum)
THEN
BEGIN
indexO ← newArray[indexN].lineNumInOtherFile + 1;
indexN ← indexN + 1;
WHILE ((indexN < totalLinesInNewFile) AND (indexO < totalLinesInOldFile))
DO
IF ((newArray[indexN].typeOfPntr = symTable) AND (oldArray[indexO].typeOfPntr = symTable) AND (newArray[indexN].symTablePntr = oldArray[indexO].symTablePntr))
THEN
BEGIN
newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
END
ELSE IF ((newArray[indexN].typeOfPntr # lineNum) OR (oldArray[indexO].typeOfPntr # lineNum) OR (newArray[indexN].lineNumInOtherFile # indexO) OR (oldArray[indexO].lineNumInOtherFile # indexN)) THEN GOTO endofthisset;
indexN ← indexN + 1;
indexO ← indexO + 1;
REPEAT endofthisset => NULL;
ENDLOOP;
END
ELSE indexN ← indexN + 1;
ENDLOOP;
StepCursor[];
--Pass 5.
indexN ← totalLinesInNewFile - 1;
WHILE indexN >= 0
DO
IF (newArray[indexN].typeOfPntr = lineNum)
THEN
BEGIN
indexO ← newArray[indexN].lineNumInOtherFile - 1;
indexN ← indexN - 1;
WHILE ((indexN >= 0) AND (indexO >= 0))
DO
IF ((newArray[indexN].typeOfPntr = symTable) AND (oldArray[indexO].typeOfPntr = symTable) AND (newArray[indexN].symTablePntr = oldArray[indexO].symTablePntr))
THEN
BEGIN
newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
END
ELSE IF ((newArray[indexN].typeOfPntr # lineNum) OR (oldArray[indexO].typeOfPntr # lineNum) OR (newArray[indexN].lineNumInOtherFile # indexO) OR (oldArray[indexO].lineNumInOtherFile # indexN)) THEN GOTO endofthisset;
indexN ← indexN - 1;
indexO ← indexO - 1;
REPEAT endofthisset => NULL;
ENDLOOP;
END
ELSE indexN ← indexN - 1;
ENDLOOP;
StepCursor[];
--Passes 6 and 7.
BEGIN
CancelMatch: PROCEDURE[array1, array2: DESCRIPTOR FOR ARRAY [0..0) OF FileArrayEntry, totalNumOfLinesFile1, index: INTEGER] =
BEGIN
UNTIL (array1[index].typeOfPntr = symTable)
DO
array1[index].typeOfPntr ← symTable;
array2[array1[index].lineNumInOtherFile].typeOfPntr ← symTable;
index ← index + 1;
IF ((index >= totalNumOfLinesFile1) OR (array1[index].lineNumInOtherFile # array1[index - 1].lineNumInOtherFile + 1)) THEN EXIT;
ENDLOOP;
END;
--Pass 6.
IF switchLinesForMatch > 1
THEN
BEGIN
indexN ← 0;
WHILE (indexN < totalLinesInNewFile)
DO
IF newArray[indexN].typeOfPntr = lineNum
THEN
BEGIN
oldIndexN: INTEGER ← indexN;
indexN ← indexN + 1;
WHILE ((indexN < totalLinesInNewFile) AND (newArray[indexN].lineNumInOtherFile = newArray[indexN - 1].lineNumInOtherFile + 1))
DO indexN ← indexN + 1; ENDLOOP;
IF (((indexN - oldIndexN) < switchLinesForMatch) AND (indexN < totalLinesInNewFile)) THEN CancelMatch[newArray, oldArray, totalLinesInNewFile, oldIndexN];
END
ELSE indexN ← indexN + 1;
ENDLOOP;
END;
StepCursor[];
--Pass 7.
BEGIN
DumpOutDiffAndMoveAhead: PROCEDURE =
BEGIN
index: INTEGER;
LeadingNumber: PROCEDURE[char: CHARACTER, number: INTEGER] =
BEGIN
IODefs.WriteChar[char]; IODefs.WriteChar[’/]; IODefs.WriteNumber[index, numFormat]; IODefs.WriteString[") "];
END;
IF NOT anyDifferencesSeen
THEN
BEGIN
OpenDifFileAndWriteHeader[];
IODefs.WriteString[Asterisks]; IODefs.WriteLine[Asterisks];
anyDifferencesSeen ← TRUE;
END;
IF ((indexN >= totalLinesInNewFile) OR (indexO >= totalLinesInOldFile))
THEN
BEGIN
indexN ← totalLinesInNewFile;
indexO ← totalLinesInOldFile;
END
ELSE
IF newArray[indexN].typeOfPntr = lineNum
THEN indexO ← newArray[indexN].lineNumInOtherFile
ELSE indexN ← oldArray[indexO].lineNumInOtherFile;
FOR index IN [startDifO..indexO)
DO LeadingNumber[’1, index]; IODefs.WriteLine[oldArray[index].symTablePntr.text]; ENDLOOP;
FOR index IN [indexO..indexO + switchLinesForContext)
WHILE (index < totalLinesInOldFile)
DO LeadingNumber[’1, index]; IODefs.WriteLine[oldArray[index].symTablePntr.text]; ENDLOOP;
IODefs.WriteLine[Asterisks];
FOR index IN [startDifN..indexN)
DO LeadingNumber[’2, index]; IODefs.WriteLine[newArray[index].symTablePntr.text]; ENDLOOP;
FOR index IN [indexN..indexN + switchLinesForContext)
WHILE (index < totalLinesInNewFile)
DO LeadingNumber[’2, index]; IODefs.WriteLine[newArray[index].symTablePntr.text]; ENDLOOP;
IODefs.WriteString[Asterisks]; IODefs.WriteLine[Asterisks];
index ← indexN + 1;
WHILE ((index < totalLinesInNewFile) AND (newArray[index].typeOfPntr # symTable))
DO
IF (newArray[index].lineNumInOtherFile # newArray[index - 1].lineNumInOtherFile + 1) THEN EXIT;
index ← index + 1;
ENDLOOP;
indexO ← indexO + (index - indexN);
indexN ← index;
startDifN ← indexN;
startDifO ← indexO;
END;
TryToResolveConflicts: PROCEDURE [array1, array2: DESCRIPTOR FOR ARRAY [0..0) OF FileArrayEntry, index1, index2: INTEGER] RETURNS[okToDumpDiff: BOOLEAN] =
BEGIN
lastRange1: INTEGER ← index1 + array1[index1].lineNumInOtherFile - index2;
tempIndex: INTEGER;
FOR tempIndex IN [index2..array1[index1].lineNumInOtherFile)
DO
IF array2[tempIndex].typeOfPntr = lineNum
THEN
BEGIN
IF array2[tempIndex].lineNumInOtherFile > lastRange1
THEN CancelMatch[array2, array1, totalLinesInOldFile, tempIndex]
ELSE
BEGIN
CancelMatch[array1, array2, totalLinesInNewFile, index1];
RETURN[okToDumpDiff: FALSE];
END;
END;
ENDLOOP;
RETURN[okToDumpDiff: TRUE];
END;
startDifN: INTEGER;
startDifO: INTEGER;
dumpDiff: BOOLEAN;
numFormat: IODefs.NumberFormat;
columns: [0..255] ← 1;
max: INTEGER ← MAX[totalLinesInNewFile, totalLinesInOldFile];
DO
max ← max/10;
IF max = 0 THEN EXIT;
columns ← columns + 1;
ENDLOOP;
numFormat ← [base: 10, zerofill: TRUE, unsigned: TRUE, columns: columns];
indexN ← 0;
indexO ← 0;
WHILE ((indexN < totalLinesInNewFile) AND (indexO < totalLinesInOldFile) AND (newArray[indexN].typeOfPntr = lineNum) AND (oldArray[indexO].typeOfPntr = lineNum) AND (newArray[indexN].lineNumInOtherFile = indexO) AND (oldArray[indexO].lineNumInOtherFile = indexN))
DO
indexN ← indexN + 1;
indexO ← indexO + 1;
ENDLOOP;
startDifN ← indexN;
startDifO ← indexO;
dumpDiff ← FALSE;
WHILE ((indexN < totalLinesInNewFile) AND (indexO < totalLinesInOldFile))
DO
IF ((newArray[indexN].typeOfPntr = lineNum) OR (oldArray[indexO].typeOfPntr = lineNum))
THEN
BEGIN
IF ((newArray[indexN].typeOfPntr = lineNum) AND (oldArray[indexO].typeOfPntr = lineNum))
THEN
BEGIN
IF ((newArray[indexN].lineNumInOtherFile = indexO) AND (oldArray[indexO].lineNumInOtherFile = indexN)) THEN GOTO dumpoutthedifference;
IF (newArray[indexN].lineNumInOtherFile - indexO) > (oldArray[indexO].lineNumInOtherFile - indexN)
THEN CancelMatch[newArray, oldArray, totalLinesInNewFile, indexN]
ELSE CancelMatch[oldArray, newArray, totalLinesInOldFile, indexO];
END;
IF (newArray[indexN].typeOfPntr = lineNum)
THEN dumpDiff ← TryToResolveConflicts[newArray, oldArray, indexN, indexO]
ELSE dumpDiff ← TryToResolveConflicts[oldArray, newArray, indexO, indexN];
EXITS
dumpoutthedifference => dumpDiff ← TRUE;
END;
IF dumpDiff
THEN
BEGIN
DumpOutDiffAndMoveAhead[];
dumpDiff ← FALSE;
END
ELSE
BEGIN
indexN ← indexN + 1;
indexO ← indexO + 1;
END;
ENDLOOP;
IF ((startDifN < totalLinesInNewFile) OR (startDifO < totalLinesInOldFile)) THEN DumpOutDiffAndMoveAhead[];
END;
StepCursor[];
IF currentStepCursor # TotalNStepsCursor THEN Alarm[];
SystemDefs.FreeSegment[newArrayPntr];
SystemDefs.FreeSegment[oldArrayPntr];
IF anyDifferencesSeen
THEN
BEGIN
msgString: STRING = "Differences written on file ";
tempString: STRING ← SystemDefs.AllocateHeapString[difFileName.length + msgString.length + 1];
StringDefs.AppendString[tempString, msgString];
StringDefs.AppendString[tempString, difFileName];
StringDefs.AppendString[tempString, "."];
FinishUp[openDifFile: FALSE, writeAndCloseDifFile: TRUE, difMsg: "
End of differences seen.", displayMsg: tempString, pause: FALSE];
END
ELSE
FinishUp[openDifFile: (NOT switchPause), writeAndCloseDifFile: (NOT switchPause), difMsg: "No differences encountered.", displayMsg: "No differences encountered.", pause: switchPause];
END;
END.
Edit Log
Initial: by Karen Kolling: November 13, 1979 12:18 PM
Change: March 11, 1980 11:40 AM: Version 1.1. increased pages for display in help message from 20 to 25 so top of msg doesn’t scroll off screen for larger system fonts. Added output of CR as first character, to avoid mesa image bug that prefixed mesa.typescript with garbage.
Change: May 5, 1980 4:04 PM: Version 1.4. removed quick and dirty that required that each file be read from the disk twice. changed dif file name from waterlily.dif to <firstfilename>.dif. added global switch /p.
Change: May 8, 1980 5:39 PM: Version 1.5. changed array allocations to use segments and then deallocated them so room at end to prevent punt.
Change: January 8, 1981 12:06 PM: Version 1.6. changed the Desc assignments to newArray and oldArray to include the number of lines, for mesa 6.