WaterlilyImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Russ Atkinson, December 3, 1984 7:01:51 pm PST
Kolling on December 12, 1983 12:15 pm
DIRECTORY
BasicTime USING [Now],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
FS USING [defaultStreamOptions, Error, GetName, OpenFile, OpenFileFromStream, StreamOpen, StreamOptions],
IO USING [Close, EndOf, GetIndex, GetLineRope, Put, PutChar, PutF, PutRope, STREAM],
ProcessExtras USING [CheckForAbort],
Rope USING [Cat, Concat, Fetch, Find, Flatten, FromChar, Length, Match, ROPE, Substr],
WaterlilyDefs USING [SymbolTableEntry, SymbolTableKey],
WaterlilyOrderedSymbolTable USING [CreateTable, EnumerateIncreasing, Initialize, Insert, Lookup, Table];
WaterlilyImpl: CEDAR PROGRAM
IMPORTS BasicTime, Commander, CommandTool, FS, IO, WaterlilyOrderedSymbolTable, ProcessExtras, Rope = {
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
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.
SymbolTableProcedure: TYPE = SAFE PROC [symbolTableKey: WaterlilyDefs.SymbolTableKey] RETURNS [stop: BOOL];
LongHelpMsg: ROPE =
"Waterlily compares two source files. The command format is one of:
 Waterlily file1 file2
 Waterlily difFile ← file1 file2
where switches are syntactically before any argument. Differences found are written on the difFile, with default extension '.dif'. The available switches are:
t Tioga format files (comments & formatting not seen)
b Bravo format files (formatting not seen)
u Unformatted files
i Ignore blank lines (default: TRUE)
#m # of matching lines (default: 3)
#c # of trailing context lines (default: 1)
";
WaterlilyProc: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
Alarm: ERROR = CODE;
oldArray: REF FileArray;
newArray: REF FileArray;
out: STREAM ← cmd.out;
FileArray: TYPE = RECORD[entry: SEQUENCE index: [0..LAST[CARDINAL]] OF FileArrayEntry];
FileArrayEntry: TYPE = RECORD[
symTableKey: WaterlilyDefs.SymbolTableKey,
posInThisFile: INT,
lineNumInOtherFile: INT,
typeOfPntr: {symTable, lineNum}];
totalLinesInNewFile: INT ← 0;
totalLinesInOldFile: INT ← 0;
posInNewFile: INT ← 0;
posInOldFile: INT ← 0;
newFile: STREAM;
oldFile: STREAM;
difFile: STREAM;
newFileName: ROPENIL;
oldFileName: ROPENIL;
difFileName: ROPENIL;
Asterisks: ROPE = "**************************************";
these switches are global or local.
FileType: TYPE = {tioga, bravo, unform};
switchNewFileType, switchOldFileType: FileType ← tioga;
switchIgnoreEmptyLinesNewFile, switchIgnoreEmptyLinesOldFile: BOOLTRUE;
these switches are global only.
switchLinesForMatch: INTEGER ← 3;
switchLinesForContext: INTEGER ← 1;
anyDifferencesSeen: BOOLFALSE;
indexN: INT;
indexO: INT;
duplicateList: REF DuplicateRecord ← NIL;
DuplicateRecord: TYPE = RECORD[
lineNum: INT,
symbolTableKey: WaterlilyDefs.SymbolTableKey,
posInThisFile: INT,
nextDupRec: REF DuplicateRecord];
SetSymbolTableEntry: PROC [nextLine: ROPE, newOrOld: {new, old}, lineNumCurrentFile: INT, posInCurrentFile: INT, leadingChar: CHAR] = {
symbolTableKey: WaterlilyDefs.SymbolTableKey;
IF (symbolTableKey ← WaterlilyOrderedSymbolTable.Lookup[symbolTable, nextLine]) = NIL
THEN {
symbolTableKey ← NEW[WaterlilyDefs.SymbolTableEntry ← [
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),
posInOldFile: posInCurrentFile,
posInNewFile: posInCurrentFile,
rope: nextLine,
rbLLink: , rbRLink: , rbColor: ]];
WaterlilyOrderedSymbolTable.Insert[symbolTable, symbolTableKey, symbolTableKey.rope];
}
ELSE SELECT newOrOld FROM
new => {
times: INT ← symbolTableKey.timesInNewFile;
IF times = 0
THEN {
symbolTableKey.lineNumberInNewFile ← lineNumCurrentFile;
symbolTableKey.posInNewFile ← posInCurrentFile;
}
ELSE EnterDuplicateRecord[symbolTableKey, lineNumCurrentFile, posInCurrentFile];
IF times < 2 THEN symbolTableKey.timesInNewFile ← times + 1;
};
ENDCASE => {
times: INT ← symbolTableKey.timesInOldFile;
IF times = 0
THEN {
symbolTableKey.lineNumberInOldFile ← lineNumCurrentFile;
symbolTableKey.posInOldFile ← posInCurrentFile;
}
ELSE EnterDuplicateRecord[symbolTableKey, lineNumCurrentFile, posInCurrentFile];
IF times < 2 THEN symbolTableKey.timesInOldFile ← times + 1;
};
};
EnterDuplicateRecord: PROC [symbolTableKey: WaterlilyDefs.SymbolTableKey, lineNumCurrentFile: INT, posInCurrentFile: INT] = {
tempDupRec: REF DuplicateRecord ← NEW[DuplicateRecord ← [lineNumCurrentFile, symbolTableKey, posInCurrentFile, duplicateList]];
duplicateList ← tempDupRec;
};
EndOfFile: SIGNAL = CODE;
ReadLineFromFile: PROC [in: STREAM, localSwitchFileType: FileType, localSwitchIgnoreEmpties: BOOL] RETURNS [line: ROPE, char: CHAR, pos: INT] = {
DO
skipLine: BOOL;
index: INT;
ProcessExtras.CheckForAbort[];
IF in.EndOf[] THEN SIGNAL EndOfFile;
pos ← in.GetIndex[];
line ← in.GetLineRope[];
IF localSwitchFileType = bravo AND
(NOT((index ← Rope.Find[line, Rope.FromChar['\032], 0, ]) = -1))
THEN line ← Rope.Substr[line, 0, index];
[char, skipLine] ← GetLeadingNonSemiBlankCharacter[line, localSwitchIgnoreEmpties];
IF NOT skipLine THEN EXIT;
ENDLOOP;
};
FinishUp: PROC[difMsg: ROPE] = {
IF difFile = NIL THEN OpenDifFileAndWriteHeader[];
IO.PutRope[difFile, difMsg];
IO.PutRope[difFile, "\n"];
IO.Close[difFile];
IO.PutRope[out, difMsg];
IO.PutRope[out, "\n"];
};
WorkingMsg: PROC = {
ProcessExtras.CheckForAbort[];
IO.PutChar[out, '.];
};
OpenDifFileAndWriteHeader: PROC = {
difFileName ← DefaultExtension[difFileName, ".dif"];
difFile ← FS.StreamOpen[difFileName, $create];
difFile.PutF["\nWaterlily\n run on %g\n File 1: %g\n File 2: %g\n\n",
[time[BasicTime.Now[]]], [rope[oldFileName]], [rope[newFileName]]];
};
OpenFile: PROC [tempFileName: ROPE, switchFileType: FileType] RETURNS [fileName: ROPE, file: STREAM] = {
streamOptions: FS.StreamOptions ← FS.defaultStreamOptions;
streamOptions[tiogaRead] ← (switchFileType = tioga);
tempFileName ← DefaultExtension[tempFileName, ".mesa"];
file ← FS.StreamOpen[tempFileName, $read, streamOptions];
fileName ← FS.GetName[FS.OpenFileFromStream[file]].fullFName;
};
start here.
symbolTable: WaterlilyOrderedSymbolTable.Table;
result ← NIL;
msg ← NIL;
WaterlilyOrderedSymbolTable.Initialize[
NEW[WaterlilyDefs.SymbolTableEntry], NEW[WaterlilyDefs.SymbolTableEntry]];
symbolTable ← WaterlilyOrderedSymbolTable.CreateTable[header: NEW[WaterlilyDefs.SymbolTableEntry]];
Process arguments
{
args: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← "Syntax error."; GO TO fatalError}
];
fileCount: NAT ← 0;
defaultFileType: FileType ← tioga;
ignoreEmptyLines: BOOLTRUE;
FOR i: NAT IN [1..args.argc) DO
arg: ROPE = args[i];
sense: BOOLTRUE;
IF Rope.Match["-*", arg] THEN {
Switches specified here
len: NAT ← Rope.Length[arg];
number: INT ← 0;
FOR i: NAT IN [1..len) DO
c: CHAR ← Rope.Fetch[arg, i];
SELECT c FROM
'~ => sense ← NOT sense;
'b, 'B => defaultFileType ← bravo;
't, 'T => defaultFileType ← tioga;
'u, 'U => defaultFileType ← unform;
'i, 'I => ignoreEmptyLines ← sense;
IN ['0..'9] => {number ← number*10 + (c-'0); LOOP};
'm, 'M => IF number >= 1 THEN switchLinesForMatch ← number;
'c, 'C => IF number >= 1 THEN switchLinesForContext ← number;
ENDCASE;
number ← 0;
ENDLOOP;
LOOP;
};
IF Rope.Match["←", arg] THEN {
The old file name is really the dif file name
IF fileCount # 1 THEN GO TO usageError;
fileCount ← 0;
difFileName ← oldFileName;
LOOP;
};
SELECT fileCount FROM
0 => {
fileCount ← 1;
switchOldFileType ← defaultFileType;
switchIgnoreEmptyLinesOldFile ← ignoreEmptyLines;
oldFileName ← arg;
difFileName ← ShortName[arg];
};
1 => {
fileCount ← 2;
switchNewFileType ← defaultFileType;
switchIgnoreEmptyLinesNewFile ← ignoreEmptyLines;
newFileName ← arg;
};
ENDCASE => {GO TO usageError};
ENDLOOP;
IF fileCount # 2 THEN GO TO usageError;
{
ENABLE FS.Error => {
IF error.group # bug THEN msg ← error.explanation;
GO TO fatalError;
};
[oldFileName, oldFile] ← OpenFile[oldFileName, switchOldFileType];
[newFileName, newFile] ← OpenFile[newFileName, switchNewFileType];
};
out.PutRope["Comparing "];
WorkingMsg[];
};
Pass 1 & 2.
{
ScanDuplicateList: PROC[array: REF FileArray] = {
UNTIL duplicateList = NIL DO
array[duplicateList.lineNum] ← [symTableKey: duplicateList.symbolTableKey,
posInThisFile: duplicateList.posInThisFile, lineNumInOtherFile: 0, typeOfPntr:
symTable];
duplicateList ← duplicateList.nextDupRec;
ENDLOOP;
};
Pass 1.
nextLine: ROPENIL;
leadingChar: CHAR;
DO
[nextLine, leadingChar, posInNewFile] ← ReadLineFromFile[newFile, switchNewFileType,
switchIgnoreEmptyLinesNewFile ! EndOfFile => GOTO donewithfile];
SetSymbolTableEntry[nextLine, new, totalLinesInNewFile, posInNewFile, leadingChar];
totalLinesInNewFile ← totalLinesInNewFile + 1;
REPEAT donewithfile => {
newFile.Close[FALSE];
newFile ← NIL;
IF totalLinesInNewFile = 0 THEN GOTO emptyfile;
};
ENDLOOP;
WorkingMsg[];
Pass 1.5.
{
Test: SymbolTableProcedure = TRUSTED {
IF (symbolTableKey.timesInNewFile >= 1) THEN
newArray[symbolTableKey.lineNumberInNewFile] ← [symTableKey: symbolTableKey, posInThisFile: symbolTableKey.posInNewFile, lineNumInOtherFile: 0, typeOfPntr: symTable];
RETURN[FALSE];
};
newArray ← NEW[FileArray[totalLinesInNewFile]];
WaterlilyOrderedSymbolTable.EnumerateIncreasing[symbolTable, Test];
ScanDuplicateList[newArray];
WorkingMsg[];
};
Pass 2.
DO
[nextLine, leadingChar, posInOldFile] ← ReadLineFromFile[oldFile, switchOldFileType,
switchIgnoreEmptyLinesOldFile ! EndOfFile => GOTO donewithfile];
SetSymbolTableEntry[nextLine, old, totalLinesInOldFile, posInOldFile, leadingChar];
totalLinesInOldFile ← totalLinesInOldFile + 1;
REPEAT donewithfile => {
oldFile.Close[FALSE];
oldFile ← NIL;
IF totalLinesInOldFile = 0 THEN GOTO emptyfile;
};
ENDLOOP;
WorkingMsg[];
Pass 2.5.
{
Test: SymbolTableProcedure =
TRUSTED {
IF (symbolTableKey.timesInOldFile >= 1)
THEN oldArray[symbolTableKey.lineNumberInOldFile] ← [symTableKey:
symbolTableKey, posInThisFile: symbolTableKey.posInOldFile, lineNumInOtherFile: 0,
typeOfPntr: symTable];
RETURN[FALSE];
};
oldArray ← NEW[FileArray[totalLinesInOldFile]];
WaterlilyOrderedSymbolTable.EnumerateIncreasing[symbolTable, Test];
};
ScanDuplicateList[oldArray];
WorkingMsg[];
EXITS emptyfile => FinishUp["At least one of these files is effectively empty."];
};
Pass 3.
{
Test: SymbolTableProcedure = TRUSTED {
ProcessExtras.CheckForAbort[];
IF ((symbolTableKey.timesInOldFile = 1) AND (symbolTableKey.timesInNewFile = 1))
THEN {
newArray[symbolTableKey.lineNumberInNewFile].typeOfPntr ← lineNum;
newArray[symbolTableKey.lineNumberInNewFile].lineNumInOtherFile ←
symbolTableKey.lineNumberInOldFile;
oldArray[symbolTableKey.lineNumberInOldFile].typeOfPntr ← lineNum;
oldArray[symbolTableKey.lineNumberInOldFile].lineNumInOtherFile ←
symbolTableKey.lineNumberInNewFile;
};
RETURN[FALSE];
};
WaterlilyOrderedSymbolTable.EnumerateIncreasing[symbolTable, Test];
WorkingMsg[];
};
Pass 4.
{
indexN ← 0;
WHILE indexN < totalLinesInNewFile DO
ProcessExtras.CheckForAbort[];
IF (newArray[indexN].typeOfPntr = lineNum)
THEN {
indexO ← newArray[indexN].lineNumInOtherFile + 1;
indexN ← indexN + 1;
WHILE ((indexN < totalLinesInNewFile) AND (indexO < totalLinesInOldFile)) DO
SELECT TRUE FROM
((newArray[indexN].typeOfPntr = symTable) AND
(oldArray[indexO].typeOfPntr = symTable) AND
(newArray[indexN].symTableKey = oldArray[indexO].symTableKey)) => {
newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
};
((newArray[indexN].typeOfPntr # lineNum) OR
(oldArray[indexO].typeOfPntr # lineNum) OR
(newArray[indexN].lineNumInOtherFile # indexO) OR
(oldArray[indexO].lineNumInOtherFile # indexN)) => EXIT;
ENDCASE;
indexN ← indexN + 1;
indexO ← indexO + 1;
ENDLOOP;
}
ELSE indexN ← indexN + 1;
ENDLOOP;
WorkingMsg[];
};
Pass 5.
{
indexN ← totalLinesInNewFile - 1;
WHILE indexN >= 0 DO
ProcessExtras.CheckForAbort[];
IF (newArray[indexN].typeOfPntr = lineNum)
THEN { indexO ← newArray[indexN].lineNumInOtherFile - 1;
indexN ← indexN - 1;
WHILE ((indexN >= 0) AND (indexO >= 0)) DO
ProcessExtras.CheckForAbort[];
SELECT TRUE FROM
((newArray[indexN].typeOfPntr = symTable) AND (oldArray[indexO].typeOfPntr = symTable) AND (newArray[indexN].symTableKey = oldArray[indexO].symTableKey)) => {
newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
};
((newArray[indexN].typeOfPntr # lineNum) OR (oldArray[indexO].typeOfPntr # lineNum) OR (newArray[indexN].lineNumInOtherFile # indexO) OR (oldArray[indexO].lineNumInOtherFile # indexN)) =>
EXIT;
ENDCASE;
indexN ← indexN - 1;
indexO ← indexO - 1;
ENDLOOP;
}
ELSE indexN ← indexN - 1;
ENDLOOP;
WorkingMsg[];
};
Passes 6 and 7.
{
CancelMatch: PROC[array1, array2: REF FileArray, totalNumOfLinesFile1, index: INT] = {
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;
};
Pass 6.
IF switchLinesForMatch > 1 THEN {
indexN ← 0;
WHILE (indexN < totalLinesInNewFile) DO
ProcessExtras.CheckForAbort[];
IF newArray[indexN].typeOfPntr = lineNum
THEN {
oldIndexN: INT ← 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];
}
ELSE indexN ← indexN + 1;
ENDLOOP;
};
WorkingMsg[];
Pass 7.
{
DumpOutDiffAndMoveAhead: PROC = {
index: INT;
LeadingNumber: PROC[char: CHAR, number: INT] = {
must be a better way to do this.
leadingZeroes: CARDINAL ← columns - 1;
tempIndex: INT ← number;
ProcessExtras.CheckForAbort[];
DO
tempIndex ← tempIndex/10;
IF tempIndex = 0 THEN EXIT;
leadingZeroes ← leadingZeroes - 1;
ENDLOOP;
difFile.PutF["%g/", [character[char]]];
THROUGH [0..leadingZeroes) DO difFile.PutChar['0]; ENDLOOP;
difFile.Put[[integer[number]]];
difFile.PutRope[") "];
};
IF NOT anyDifferencesSeen THEN {
OpenDifFileAndWriteHeader[];
difFile.PutF["%g%g\n", [rope[Asterisks]], [rope[Asterisks]]];
anyDifferencesSeen ← TRUE;
};
SELECT TRUE FROM
(indexN >= totalLinesInNewFile) OR (indexO >= totalLinesInOldFile) => {
indexN ← totalLinesInNewFile;
indexO ← totalLinesInOldFile;
};
newArray[indexN].typeOfPntr = lineNum =>
indexO ← newArray[indexN].lineNumInOtherFile
ENDCASE =>
indexN ← oldArray[indexO].lineNumInOtherFile;
FOR index IN [startDifO..indexO) DO
LeadingNumber['1, oldArray[index].posInThisFile];
difFile.PutF["%g\n", [rope[oldArray[index].symTableKey.rope] ]];
ENDLOOP;
FOR index IN [indexO..indexO + switchLinesForContext) WHILE (index < totalLinesInOldFile) DO
LeadingNumber['1, oldArray[index].posInThisFile];
difFile.PutF["%g\n", [rope[oldArray[index].symTableKey.rope] ]];
ENDLOOP;
difFile.PutF["%g\n", [rope[Asterisks]]];
FOR index IN [startDifN..indexN) DO
LeadingNumber['2, newArray[index].posInThisFile];
difFile.PutF["%g\n", [rope[newArray[index].symTableKey.rope] ]];
ENDLOOP;
FOR index IN [indexN..indexN + switchLinesForContext) WHILE (index < totalLinesInNewFile) DO
LeadingNumber['2, newArray[index].posInThisFile];
difFile.PutF["%g\n", [rope[newArray[index].symTableKey.rope] ]];
ENDLOOP;
difFile.PutF["%g%g\n", [rope[Asterisks]], [rope[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;
};
TryToResolveConflicts: PROC [array1, array2: REF FileArray, index1, index2: INT] RETURNS[okToDumpDiff: BOOL] = {
lastRange1: INT ← index1 + array1[index1].lineNumInOtherFile - index2;
tempIndex: INT;
FOR tempIndex IN [index2..array1[index1].lineNumInOtherFile) DO
IF array2[tempIndex].typeOfPntr = lineNum THEN {
IF array2[tempIndex].lineNumInOtherFile > lastRange1
THEN CancelMatch[array2, array1, totalLinesInOldFile, tempIndex]
ELSE {
CancelMatch[array1, array2, totalLinesInNewFile, index1];
RETURN[FALSE];
};
};
ENDLOOP;
RETURN[TRUE];
};
startDifN: INT;
startDifO: INT;
dumpDiff: BOOL;
columns: [0..255] ← 1;
max: INTMAX[posInNewFile, posInOldFile];
DO
max ← max/10;
IF max = 0 THEN EXIT;
columns ← columns + 1;
ENDLOOP;
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 {
IF ((newArray[indexN].typeOfPntr = lineNum) AND (oldArray[indexO].typeOfPntr = lineNum)) THEN {
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];
};
IF (newArray[indexN].typeOfPntr = lineNum)
THEN dumpDiff ← TryToResolveConflicts[newArray, oldArray, indexN,
indexO]
ELSE dumpDiff ← TryToResolveConflicts[oldArray, newArray, indexO,
indexN];
EXITS dumpoutthedifference => dumpDiff ← TRUE;
};
IF dumpDiff
THEN {
DumpOutDiffAndMoveAhead[];
dumpDiff ← FALSE;
}
ELSE {
indexN ← indexN + 1;
indexO ← indexO + 1;
};
ENDLOOP;
IF ((startDifN < totalLinesInNewFile) OR (startDifO < totalLinesInOldFile)) THEN
DumpOutDiffAndMoveAhead[];
WorkingMsg[];
};
IF anyDifferencesSeen
THEN {
FinishUp[Rope.Cat[" differences written on file ", difFileName, "."]]}
ELSE
FinishUp[" no differences encountered.\n"];
};
EXITS
usageError => {msg ← "Usage error: Waterlily file1 file2\n"; result ← $Failed};
fatalError => {result ← $Failed};
};
GetLeadingNonSemiBlankCharacter: PROC [line: ROPE, localSwitchIgnoreEmpties: BOOL] RETURNS [leadingChar: CHAR, skipLine: BOOL] = {
index: CARDINAL ← 0;
length: CARDINAL = Rope.Length[line];
WHILE index < length DO
char: CHAR ← Rope.Fetch[line, index];
SELECT char FROM
' , '\t => RETURN[leadingChar: char, skipLine: FALSE];
ENDCASE => index ← index + 1;
ENDLOOP;
here on an "empty" line.
RETURN[leadingChar: ' , skipLine: localSwitchIgnoreEmpties];
};
ShortName: PROC [name: ROPE] RETURNS [ROPE] = {
bang: INT ← Rope.Length[name];
pos: INT ← bang;
WHILE (pos ← pos - 1) > 0 DO
SELECT Rope.Fetch[name, pos] FROM
'>, '/, '] => {pos ← pos + 1; EXIT};
'!, '. => bang ← pos;
ENDCASE;
ENDLOOP;
RETURN [Rope.Flatten[name, pos, bang - pos]];
};
DefaultExtension: PROC [name: ROPE, ext: ROPE] RETURNS [ROPE] = {
len: INT ← Rope.Length[name];
pos: INT ← len;
WHILE (pos ← pos - 1) > 0 DO
SELECT Rope.Fetch[name, pos] FROM
'>, '/, '] => EXIT;
'., '! => RETURN [name];
ENDCASE;
ENDLOOP;
RETURN [Rope.Concat[name, ext]];
};
Commander.Register[key: "Waterlily", proc: WaterlilyProc, doc: LongHelpMsg, clientData: ];
}.
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.
Change: 22-Mar-82 17:21:45: Version 1.7 for Viewers. made a number of internal changes to use ropes and iostream, took out /h switch since userexec handles help messages diferently. changed switch /b from /b and /-b to /b, /t, /u.
Change: July 7, 1982 12:40 pm: Version 1.8. io => iostream. added "Comparing" message, used new io facility for tioga switch.
Change: October 1, 1982 5:30 pm: Version 1.9. updated long help msg about tioga comment nodes; converted to Cedar 3.4.
Change: November 16, 1982 5:51 pm: Version 1.10. converted to Cedar 3.5.
Change: February 23, 1983 12:28 pm: Version 1.11. position rather than line numbers to dif file.
Change: February 28, 1983 2:21 pm: Version 1.12. converted to Cedar 4.0.
Change: March 15, 1983 1:35 pm: Version 1.13. switch added to allow halt instead of overwriting an existing dif file. Also, if pause is turned off and no differences are seen, doesn't write a dif file.
Change: October 12, 1983 2:56 pm: Version 1.14. conversion to 5.0.