CompareImpl.mesa
Last edited by:
Kolling on March 15, 1983 4:23 pm
Wyatt on November 4, 1983 12:53 pm
DIRECTORY
Commander USING[CommandProc, Register],
FileIO USING[Open, OpenFailed],
IO USING[char, CharProc, Close, CR, EndOf, EndOfStream, GetChar, GetIndex, GetInt, GetOutputStreamRope, GetSequence, int, LineAtATime, Put, PutChar, PutF, PutRope, Reset, rope, RIS, ROS, Signal, SP, STREAM, TAB, time],
OrderedSymbolTable USING[CreateTable, EnumerateIncreasing, Initialize, Insert, Lookup, Table],
Rope USING[Cat, Fetch, Find, FromChar, Length, ROPE, Substr],
CompareDefs USING[SymbolTableEntry, SymbolTableKey];
CompareImpl: CEDAR PROGRAM
IMPORTS Commander, FileIO, IO, OST: OrderedSymbolTable, Rope
= 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.
SymbolTableKey: TYPE = CompareDefs.SymbolTableKey;
SymbolTableEntry: TYPE = CompareDefs.SymbolTableEntry;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TerminatingSignal: ERROR = CODE;
FileArray: TYPE = REF FileArrayRep;
FileArrayRep: TYPE = RECORD[SEQUENCE index: NAT OF FileArrayEntry];
FileArrayEntry:
TYPE =
RECORD[
symTableKey: SymbolTableKey,
posInThisFile: INT,
lineNumInOtherFile: INTEGER,
typeOfPntr: {symTable, lineNum}];
CompareProc: Commander.CommandProc
--[cmd: Handle]-- = {
ENABLE TerminatingSignal =>
GOTO done;
-- what a crock. where did StopMesa go.....
commandLineStream: STREAM = IO.RIS[cmd.commandLine];
Alarm: ERROR = CODE;
oldArray, newArray: FileArray ← NIL;
totalLinesInNewFile: INTEGER ← 0;
totalLinesInOldFile: INTEGER ← 0;
posInNewFile: INT ← 0;
posInOldFile: INT ← 0;
newFile: STREAM;
oldFile: STREAM;
difFile: STREAM;
newFileName: ROPE ← NIL;
oldFileName: ROPE ← NIL;
difFileName: ROPE ← NIL;
Asterisks: ROPE = "**************************************";
these switches are global or local.
FileType: TYPE = {tioga, bravo, unform};
switchNewFileType, switchOldFileType: FileType ← tioga;
switchIgnoreEmptyLinesNewFile, switchIgnoreEmptyLinesOldFile: BOOLEAN ← TRUE;
these switches are global only.
switchLinesForMatch: INTEGER ← 3;
switchLinesForContext: INTEGER ← 1;
switchPause: BOOLEAN ← TRUE;
switchOverwrite: BOOLEAN ← TRUE;
anyDifferencesSeen: BOOLEAN ← FALSE;
indexN, indexO: INTEGER;
SymbolTableProcedure:
TYPE =
PROC[symbolTableKey: SymbolTableKey]
RETURNS [stop: BOOLEAN];
duplicateList: REF DuplicateRecord ← NIL;
DuplicateRecord:
TYPE =
RECORD[lineNum:
INTEGER, symbolTableKey:
SymbolTableKey, posInThisFile: INT, nextDupRec: REF DuplicateRecord];
SetSymbolTableEntry:
PROC [nextLine:
ROPE, newOrOld: {new, old},
lineNumCurrentFile:
INTEGER, posInCurrentFile:
INT, leadingChar:
CHAR] =
{
symbolTableKey: SymbolTableKey;
IF (symbolTableKey ←
OST.Lookup[symbolTable, nextLine]) =
NIL
THEN {
symbolTableKey ←
NEW[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: ]];
OST.Insert[symbolTable, symbolTableKey, symbolTableKey.rope];
}
ELSE
SELECT newOrOld
FROM
new => {
IF symbolTableKey.timesInNewFile = 0
THEN { symbolTableKey.lineNumberInNewFile ←
lineNumCurrentFile;
symbolTableKey.posInNewFile ← posInCurrentFile;
}
ELSE EnterDuplicateRecord[symbolTableKey,
lineNumCurrentFile, posInCurrentFile];
IF symbolTableKey.timesInNewFile < 2
THEN symbolTableKey.timesInNewFile ←
symbolTableKey.timesInNewFile + 1;
};
ENDCASE => {
IF symbolTableKey.timesInOldFile = 0
THEN { symbolTableKey.lineNumberInOldFile ←
lineNumCurrentFile;
symbolTableKey.posInOldFile ← posInCurrentFile;
}
ELSE EnterDuplicateRecord[symbolTableKey,
lineNumCurrentFile, posInCurrentFile];
IF symbolTableKey.timesInOldFile < 2
THEN symbolTableKey.timesInOldFile ←
symbolTableKey.timesInOldFile + 1;
};
};
EnterDuplicateRecord:
PROC [symbolTableKey: SymbolTableKey,
lineNumCurrentFile:
INTEGER, posInCurrentFile:
INT] =
{
tempDupRec:
REF DuplicateRecord ←
NEW[DuplicateRecord
← [lineNumCurrentFile, symbolTableKey, posInCurrentFile, duplicateList]];
duplicateList ← tempDupRec;
};
EndOfFile: SIGNAL = CODE;
ReadLineFromFile:
PROC [InputFile:
STREAM, localSwitchFileType:
FileType, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [line: ROPE,
char:
CHAR, pos:
INT] = {
SkipCR: IO.CharProc = { RETURN[(char = IO.CR), TRUE]; };
DO
skipLine: BOOLEAN;
index: INT;
IF InputFile.EndOf[] THEN SIGNAL EndOfFile;
pos ← InputFile.GetIndex[];
line ← InputFile.GetSequence[IO.LineAtATime]; -- late, lamented
[] ← InputFile.GetSequence[SkipCR]; -- IO.GetLine.
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;
};
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:
PROC [line:
ROPE,
localSwitchIgnoreEmpties: BOOLEAN] RETURNS [leadingChar: CHAR,
skipLine:
BOOLEAN] =
{
index: CARDINAL ← 0;
length: CARDINAL = Rope.Length[line];
char: CHAR;
WHILE index < length
DO
char ← Rope.Fetch[line, index];
IF ((char #
IO.
SP)
AND (char #
IO.
TAB))
THEN
GOTO found;
index ← index + 1;
REPEAT found => RETURN[leadingChar: char, skipLine: FALSE];
ENDLOOP;
here on an "empty" line.
RETURN[leadingChar: IO.SP, skipLine: localSwitchIgnoreEmpties];
};
LegalCharInFilename:
PROC[char:
CHAR]
RETURNS[
BOOLEAN] =
{
SELECT char
FROM
IN ['A..'Z],
IN ['a..'z],
IN ['0..'9], '+, '-, '., '!, '$, '[, '], '<, '>
=> RETURN[TRUE];
ENDCASE => RETURN[FALSE];
};
SkipSpacesAndTabs:
PROC[char:
CHAR, InputStream:
STREAM]
RETURNS[nextChar:
CHAR] =
{
nextChar ← char;
WHILE ((nextChar =
IO.
SP)
OR (nextChar =
IO.
TAB))
DO nextChar ← InputStream.GetChar[]; ENDLOOP;
};
TerminalHoHoDisplayOutput:
PROC[rope:
ROPE, pause:
BOOLEAN] =
{
cmd.out.PutF["%g*n", IO.rope[rope]];
IF pause
THEN { cmd.out.PutRope["HIT RETURN TO EXIT: "];
[] ← cmd.in.GetChar[ !
IO.Signal => IF ec=Rubout THEN { cmd.in.Reset[]; CONTINUE }];
};
ERROR TerminatingSignal; -- what a crock. where did StopMesa go.....
};
FinishUp:
PROC[openDifFile, writeAndCloseDifFile:
BOOLEAN, difMsg,
displayMsg:
ROPE, pause:
BOOLEAN] =
{
IF openDifFile THEN OpenDifFileAndWriteHeader[];
IF writeAndCloseDifFile
THEN {
difFile.PutF["%g*n", IO.rope[difMsg]];
difFile.Close[FALSE];
};
TerminalHoHoDisplayOutput[displayMsg, pause];
};
IllegalFileName: ERROR = CODE;
BuildDifFileName:
PROC =
{ -- quick and dirty.
length: INT ← Rope.Length[oldFileName];
firstPosofFileName: INT ← 0;
posOfLastDot: INT;
DO
index: INT ← Rope.Find[oldFileName, Rope.FromChar['>], firstPosofFileName, TRUE];
IF index = -1 THEN EXIT;
IF (firstPosofFileName ← index + 1) >= length THEN ERROR IllegalFileName;
ENDLOOP;
posOfLastDot ← Rope.Find[oldFileName, Rope.FromChar['.], firstPosofFileName, TRUE];
difFileName ← Rope.Substr[oldFileName, firstPosofFileName, (IF posOfLastDot = -1 THEN length ELSE posOfLastDot) - firstPosofFileName];
difFileName ← Rope.Cat[difFileName, ".dif"];
};
OpenDifFileAndWriteHeader:
PROC = {
-- quick and dirty.
difFile ← FileIO.Open[difFileName,
overwrite, (
IF switchOverwrite
THEN none
ELSE newOnly) !
FileIO.OpenFailed => IF why=fileAlreadyExists THEN GOTO fileAlreadyExists
];
difFile.PutF["*nCompare Version 1.14 %g*n*nDifferences between files %g and %g:*n*n",
IO.time[], IO.rope[oldFileName], IO.rope[newFileName]];
EXITS fileAlreadyExists => {
oldFile.Close[FALSE]; newFile.Close[FALSE];
TerminalHoHoDisplayOutput["Dif file already exists.", TRUE];
};
WorkingMsg:
PROC = { cmd.out.PutChar['.] };
ReadComDotCmOrPrompt:
PROC = {
newFileSeen, oldFileSeen: BOOLEAN ← FALSE;
SyntaxError: ERROR = CODE;
ProcessGlobalSwitches:
PROC[char:
CHAR]
RETURNS[nextChar:
CHAR] = {
DO
IF char = '/ THEN char ← commandLineStream.GetChar[];
SELECT char
FROM
'b, 'B => { switchNewFileType ← switchOldFileType ← bravo };
't, 'T => { switchNewFileType ← switchOldFileType ← tioga };
'u, 'U => { switchNewFileType ← switchOldFileType ← unform };
'o, 'O => switchOverwrite ← TRUE;
'p, 'P => switchPause ← TRUE;
'i, 'I => { switchIgnoreEmptyLinesNewFile ← switchIgnoreEmptyLinesOldFile ← TRUE };
'- => {
char ← commandLineStream.GetChar[];
SELECT char
FROM
'i, 'I => { switchIgnoreEmptyLinesNewFile ← switchIgnoreEmptyLinesOldFile ← FALSE };
'p, 'P => { switchPause ← FALSE; };
'o, 'O => switchOverwrite ← FALSE;
ENDCASE => ERROR SyntaxError;
};
IN ['0..'9] => {
ropeHandle: STREAM ← IO.ROS[];
number: INTEGER;
tempIndex: INTEGER ← 0;
WHILE char
IN ['0..'9]
DO
IF tempIndex > 5 THEN ERROR SyntaxError;
ropeHandle.PutChar[char];
tempIndex ← tempIndex + 1;
char ← commandLineStream.GetChar[];
ENDLOOP;
number ←
IO.GetInt[IO.RIS[ropeHandle.GetOutputStreamRope[], NIL]];
ropeHandle.Close[];
SELECT char
FROM
'm, 'M => {
IF number <1 THEN ERROR SyntaxError;
switchLinesForMatch ← number;
};
'c, 'C => switchLinesForContext ← number;
ENDCASE => ERROR SyntaxError;
};
ENDCASE => ERROR SyntaxError;
SELECT (char ← commandLineStream.GetChar[])
FROM
'-, 'b, 'B, 'i, 'I, 'p, 'P, 't, 'T, 'u, 'U, '/, IN ['0..'9] => LOOP;
IO.
SP,
IO.
TAB => { char ← SkipSpacesAndTabs[char,
commandLineStream];
IF (char = '/) THEN LOOP;
};
ENDCASE => NULL;
RETURN[char];
ENDLOOP;
};
ProcessLocalSwitches:
PROC[
defaultSwitchFileType: FileType, defaultSwitchIgnore: BOOLEAN]
RETURNS[switchFileType: FileType, switchIgnore:
BOOLEAN, nextChar:
CHAR] = {
switchFileType ← defaultSwitchFileType;
switchIgnore ← defaultSwitchIgnore;
nextChar ← commandLineStream.GetChar[];
DO
SELECT nextChar
FROM
'b, 'B => switchFileType ← bravo;
't, 'T => switchFileType ← tioga;
'u, 'U => switchFileType ← unform;
'i, 'I => switchIgnore ← TRUE;
'- =>
SELECT (nextChar ← commandLineStream.GetChar[])
FROM
'i, 'I => switchIgnore ← FALSE;
ENDCASE => ERROR SyntaxError;
ENDCASE => ERROR SyntaxError;
SELECT (nextChar ← commandLineStream.GetChar[])
FROM
'/ => nextChar ← commandLineStream.GetChar[];
'-, 'b, 'B, 'i, 'I, 't, 'T, 'u, 'U => NULL;
ENDCASE => RETURN;
ENDLOOP;
};
OpenFile:
PROC[tempFileName:
ROPE, switchFileType: FileType]
RETURNS[fileSeen:
BOOLEAN, fileName:
ROPE, file:
STREAM] =
{
file ← FileIO.Open[tempFileName, read, oldOnly, , , switchFileType #
tioga];
fileName ← Rope.Substr[tempFileName, 0, Rope.Length[tempFileName]];
fileSeen ← TRUE;
};
may or may not be given a leading character,
GetFileName:
PROC [handle:
STREAM, leadingChar:
CHAR]
RETURNS
[tempFileName:
ROPE, nextChar:
CHAR] =
{
ropeHandle: STREAM ← IO.ROS[];
IF LegalCharInFilename[leadingChar] THEN ropeHandle.PutChar[leadingChar];
WHILE LegalCharInFilename[nextChar ← handle.GetChar[]]
DO ropeHandle.PutChar[nextChar]; ENDLOOP;
tempFileName ← ropeHandle.GetOutputStreamRope[];
ropeHandle.Close[];
};
ProcessFilename:
PROC[char:
CHAR]
RETURNS[nextChar:
CHAR] =
{
tempFileName: ROPE ← NIL;
IF newFileSeen AND oldFileSeen THEN ERROR SyntaxError;
guaranteed at least one legal char on entry.
[tempFileName, nextChar] ← GetFileName[commandLineStream,
char];
IF
NOT oldFileSeen
THEN {
IF nextChar = '/
THEN
[switchOldFileType, switchIgnoreEmptyLinesOldFile, nextChar] ←
ProcessLocalSwitches[switchOldFileType, switchIgnoreEmptyLinesOldFile];
[oldFileSeen, oldFileName, oldFile] ← OpenFile[tempFileName,
switchOldFileType];
}
ELSE {
IF nextChar = '/
THEN
[switchNewFileType, switchIgnoreEmptyLinesNewFile, nextChar] ←
ProcessLocalSwitches[switchNewFileType, switchIgnoreEmptyLinesNewFile];
[newFileSeen, newFileName, newFile] ← OpenFile[tempFileName,
switchNewFileType];
};
nextChar ← SkipSpacesAndTabs[nextChar, commandLineStream];
};
PromptForFile:
PROC[typeOfFile:
ROPE, switchFileType: FileType]
RETURNS [fileSeen:
BOOLEAN, fileName:
ROPE, file:
STREAM] = {
DO
{
tempFileName: ROPE ← NIL;
nextChar: CHAR;
cmd.out.PutF["%g file: ", IO.rope[typeOfFile]];
[tempFileName, nextChar] ← GetFileName[cmd.in, IO.SP];
IF nextChar #
IO.
CR
THEN {
UNTIL nextChar =
IO.
CR
DO nextChar ← cmd.in.GetChar[];
ENDLOOP;
GOTO err;
};
IF Rope.Length[tempFileName] = 0 THEN GOTO err;
[fileSeen, fileName, file] ← OpenFile[tempFileName, switchFileType
! FileIO.OpenFailed =>
IF (why=fileNotFound) OR (why=illegalFileName) THEN GOTO err];
EXIT;
EXITS err => cmd.out.PutF["%g*n",
IO.rope["File not found or syntax error"]];
};
ENDLOOP;
};
fatalMsg: ROPE ← NIL;
{
ENABLE {
IO.EndOfStream, SyntaxError => {
fatalMsg ← "Syntax error.";
GOTO fatalError;
};
FileIO.OpenFailed => {
IF (why = fileNotFound)
OR (why = illegalFileName)
THEN {
fatalMsg ← "File not found or syntax error.";
GOTO fatalError;
};
};
};
char: CHAR ← SkipSpacesAndTabs[IO.SP, commandLineStream];
IF (char = '/) THEN char ← ProcessGlobalSwitches[char];
IF LegalCharInFilename[char] THEN char ← ProcessFilename[char];
IF LegalCharInFilename[char] THEN char ← ProcessFilename[char];
char ← SkipSpacesAndTabs[char, commandLineStream];
IF ((char # IO.CR) AND (char # ';)) THEN ERROR SyntaxError;
EXITS fatalError => {
IF oldFileSeen
THEN oldFile.Close[
FALSE];
IF newFileSeen THEN newFile.Close[FALSE];
TerminalHoHoDisplayOutput[fatalMsg, TRUE];
};
};
PromptForFile does its own error checking since it can recover.
{
ENABLE {
IO.Signal => {
IF ec = Rubout
THEN {
IF oldFileSeen THEN oldFile.Close[FALSE];
cmd.in.Reset[];
TerminalHoHoDisplayOutput["XXX
", FALSE];
};
};
};
IF
NOT oldFileSeen
THEN [oldFileSeen, oldFileName, oldFile] ← PromptForFile["Old",
switchOldFileType];
IF
NOT newFileSeen
THEN [newFileSeen, newFileName, newFile] ← PromptForFile["New",
switchNewFileType];
};
BuildDifFileName[! IllegalFileName => GOTO badDifFileName];
EXITS badDifFileName => { oldFile.Close[
FALSE]; newFile.Close[
FALSE];
TerminalHoHoDisplayOutput["Dif file would have illegal name.", TRUE]; };
};
start here.
symbolTable: OST.Table;
OST.Initialize[NEW[SymbolTableEntry], NEW[SymbolTableEntry]];
symbolTable ←
OST.CreateTable[header:
NEW[SymbolTableEntry]];
ReadComDotCmOrPrompt[];
cmd.out.PutRope["Comparing"];
WorkingMsg[];
{
ScanDuplicateList:
PROC[array: 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: ROPE ← NIL;
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];
IF totalLinesInNewFile = 0 THEN GOTO emptyfile;
};
ENDLOOP;
WorkingMsg[];
Pass 1 and 1/2.
newArray ← NEW[FileArrayRep[totalLinesInNewFile]];
{
Test: SymbolTableProcedure = {
IF (symbolTableKey.timesInNewFile >= 1)
THEN newArray[symbolTableKey.lineNumberInNewFile] ← [symTableKey:
symbolTableKey, posInThisFile: symbolTableKey.posInNewFile, lineNumInOtherFile: 0,
typeOfPntr: symTable];
RETURN[FALSE];
};
OST.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];
IF totalLinesInOldFile = 0 THEN GOTO emptyfile;
};
ENDLOOP;
WorkingMsg[];
Pass 2 and 1/2.
oldArray ← NEW[FileArrayRep[totalLinesInOldFile]];
{
Test: SymbolTableProcedure = {
IF (symbolTableKey.timesInOldFile >= 1)
THEN oldArray[symbolTableKey.lineNumberInOldFile] ← [symTableKey:
symbolTableKey, posInThisFile: symbolTableKey.posInOldFile, lineNumInOtherFile: 0,
typeOfPntr: symTable];
RETURN[FALSE];
};
OST.EnumerateIncreasing[symbolTable, Test];
};
ScanDuplicateList[oldArray];
WorkingMsg[];
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];
};
Pass 3.
{
Test: SymbolTableProcedure = {
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];
};
OST.EnumerateIncreasing[symbolTable, Test];
};
WorkingMsg[];
Pass 4.
indexN ← 0;
WHILE indexN < totalLinesInNewFile
DO
IF (newArray[indexN].typeOfPntr = lineNum)
THEN {
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].symTableKey = oldArray[indexO].symTableKey))
THEN { newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
}
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;
}
ELSE indexN ← indexN + 1;
ENDLOOP;
WorkingMsg[];
Pass 5.
indexN ← totalLinesInNewFile - 1;
WHILE indexN >= 0
DO
IF (newArray[indexN].typeOfPntr = lineNum)
THEN { 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].symTableKey = oldArray[indexO].symTableKey))
THEN { newArray[indexN].typeOfPntr ← lineNum;
oldArray[indexO].typeOfPntr ← lineNum;
newArray[indexN].lineNumInOtherFile ← indexO;
oldArray[indexO].lineNumInOtherFile ← indexN;
}
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;
}
ELSE indexN ← indexN - 1;
ENDLOOP;
WorkingMsg[];
Passes 6 and 7.
{
CancelMatch:
PROC[array1, array2: FileArray, totalNumOfLinesFile1,
index:
INTEGER] =
{
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
IF newArray[indexN].typeOfPntr = lineNum
THEN { 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];
}
ELSE indexN ← indexN + 1;
ENDLOOP;
};
WorkingMsg[];
Pass 7.
{
DumpOutDiffAndMoveAhead:
PROC =
{
index: INTEGER;
LeadingNumber:
PROC[char:
CHAR, number:
INT] =
-- must be a better way to do this.
{
leadingZeroes: CARDINAL ← columns - 1;
tempIndex: INT ← number;
DO
tempIndex ← tempIndex/10;
IF tempIndex = 0 THEN EXIT;
leadingZeroes ← leadingZeroes - 1;
ENDLOOP;
difFile.PutF["%g/", IO.char[char]];
THROUGH [0..leadingZeroes) DO difFile.PutChar['0]; ENDLOOP;
difFile.Put[IO.int[number]];
difFile.PutRope[") "];
};
IF
NOT anyDifferencesSeen
THEN { OpenDifFileAndWriteHeader[];
difFile.PutF["%g%g*n", IO.rope[Asterisks], IO.rope[Asterisks]];
anyDifferencesSeen ← TRUE;
};
IF ((indexN >= totalLinesInNewFile)
OR (indexO >= totalLinesInOldFile))
THEN { indexN ← totalLinesInNewFile;
indexO ← totalLinesInOldFile;
}
ELSE
IF newArray[indexN].typeOfPntr = lineNum
THEN indexO ← newArray[indexN].lineNumInOtherFile
ELSE indexN ← oldArray[indexO].lineNumInOtherFile;
FOR index
IN [startDifO..indexO)
DO LeadingNumber['1, oldArray[index].posInThisFile];
difFile.PutF["%g*n", IO.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", IO.rope[oldArray[index].symTableKey.rope]]; ENDLOOP;
difFile.PutF["%g*n", IO.rope[Asterisks]];
FOR index
IN [startDifN..indexN)
DO LeadingNumber['2, newArray[index].posInThisFile];
difFile.PutF["%g*n", IO.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", IO.rope[newArray[index].symTableKey.rope]]; ENDLOOP;
difFile.PutF["%g%g*n", IO.rope[Asterisks], IO.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: FileArray, index1,
index2:
INTEGER]
RETURNS[okToDumpDiff:
BOOLEAN] =
{
lastRange1: INTEGER ← index1 + array1[index1].lineNumInOtherFile - index2;
tempIndex: INTEGER;
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[okToDumpDiff: FALSE];
};
};
ENDLOOP;
RETURN[okToDumpDiff: TRUE];
};
startDifN: INTEGER;
startDifO: INTEGER;
dumpDiff: BOOLEAN;
columns: [0..255] ← 1;
max: INT ← MAX[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 {
tempRope: ROPE ← Rope.Cat["Differences written on file ", difFileName, "."];
FinishUp[openDifFile: FALSE, writeAndCloseDifFile: TRUE, difMsg: "
End of differences seen.", displayMsg: tempRope, pause: FALSE];
}
ELSE FinishUp[openDifFile:
FALSE, writeAndCloseDifFile:
FALSE, difMsg:
NIL,
displayMsg: "No differences encountered.", pause: switchPause];
};
EXITS done => NULL;
};
main body of module.
ShortHelpMsg: ROPE = "Compares two source files.";
LongHelpMsg: ROPE = "
Compare compares two source files. This is the format of its command line:
Compare <globalswitches> filename1<localswitches> filename2<localswitches>
where the global switches acceptable are:
t Tioga format files. Only nodes whose Comment property is FALSE are seen. This is the default among t, b, and u.
b Bravo format files. Everything on a line between ^Z and CR is ignored.
u Unformatted files. Neither Tioga nor Bravo, i.e., no filtering.
i Ignore blank and empty lines. Default = TRUE. -i is legal.
o Overwrite an existing dif file. Default = TRUE.
p On empty file or no difs encountered, pause and do not write a dif file. Default = TRUE. -p is legal and means don't pause, but do write a dif file for the empty case.
/#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 t, b, u, and i.
Examples of command lines are:
Compare NewPkgImpl.mesa [Indigo]<Cedar>Pkg>PkgImpl.mesa
Compare /b Foo.bravo Foo2.bravo/-i
Wate /6m OldMemo.Bravo/b Memo.tioga
Compare (this form will prompt for the filenames.)
The differences found are written on the file filename.dif, where filename is filename1 stripped of any remote path specification.
Only the form of remote path specification which uses square and angle brackets is acceptable; remote path specs using /'s will be rejected.
";
Commander.Register["Compare", CompareProc, ShortHelpMsg];
END.
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 Compare.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: by Doug Wyatt, November 4, 1983 11:34 am: Version 1.14. convert from UserExec to Commander.