--WaterlilyImpl.mesa -- Last edited by: -- Kolling on March 15, 1983 4:23 pm DIRECTORY FileIO USING[Open, OpenFailed], IO USING[char, CharProc, Close, CR, CreateOutputStreamToRope, EndOf, EndOfStream, GetChar, GetIndex, GetInt, GetOutputStreamRope, GetSequence, Handle, int, LineAtATime, Put, PutChar, PutF, PutRope, Reset, rope, RIS, Signal, SP, TAB, time], OrderedSymbolTable USING[CreateTable, EnumerateIncreasing, Initialize, Insert, Lookup, Table], Rope USING[Cat, Fetch, Find, FromChar, Length, ROPE, Substr], SafeStorage USING[NewZone], UserExec USING[ExecHandle, GetStreams, HistoryEvent, RegisterCommand], WaterlilyDefs USING[SymbolTableEntry, SymbolTableKey]; WaterlilyImpl: PROGRAM IMPORTS FileIO, IO, OST: OrderedSymbolTable, Rope, SafeStorage, UserExec, Wat: WaterlilyDefs = 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. TerminatingSignal: SIGNAL = CODE; WaterlilyProc: SAFE PROCEDURE[event: UserExec.HistoryEvent, exec: UserExec.ExecHandle, clientData: REF ANY] RETURNS[ok: BOOLEAN, msg: Rope.ROPE] = TRUSTED BEGIN ENABLE TerminatingSignal => GOTO done; -- what a crock. where did StopMesa go..... Alarm: ERROR = CODE; oldArray: REF FileArray; newArray: REF FileArray; FileArray: TYPE = RECORD[entry: SEQUENCE index: [0..LAST[CARDINAL]] OF FileArrayEntry]; FileArrayEntry: TYPE = RECORD[ symTableKey: Wat.SymbolTableKey, posInThisFile: INT, lineNumInOtherFile: INTEGER, typeOfPntr: {symTable, lineNum}]; totalLinesInNewFile: INTEGER _ 0; totalLinesInOldFile: INTEGER _ 0; posInNewFile: INT _ 0; posInOldFile: INT _ 0; newFile: IO.Handle; oldFile: IO.Handle; difFile: IO.Handle; newFileName: Rope.ROPE _ NIL; oldFileName: Rope.ROPE _ NIL; difFileName: Rope.ROPE _ NIL; Asterisks: Rope.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 = SAFE PROCEDURE[symbolTableKey: Wat.SymbolTableKey] RETURNS [stop: BOOLEAN]; duplicateList: REF DuplicateRecord _ NIL; DuplicateRecord: TYPE = RECORD[lineNum: INTEGER, symbolTableKey: Wat.SymbolTableKey, posInThisFile: INT, nextDupRec: REF DuplicateRecord]; SetSymbolTableEntry: PROCEDURE [nextLine: Rope.ROPE, newOrOld: {new, old}, lineNumCurrentFile: INTEGER, posInCurrentFile: INT, leadingChar: CHARACTER] = BEGIN symbolTableKey: Wat.SymbolTableKey; IF (symbolTableKey _ OST.Lookup[symbolTable, nextLine]) = NIL THEN BEGIN symbolTableKey _ SymbolTableZone.NEW[Wat.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]; END ELSE SELECT newOrOld FROM new => BEGIN IF symbolTableKey.timesInNewFile = 0 THEN BEGIN symbolTableKey.lineNumberInNewFile _ lineNumCurrentFile; symbolTableKey.posInNewFile _ posInCurrentFile; END ELSE EnterDuplicateRecord[symbolTableKey, lineNumCurrentFile, posInCurrentFile]; IF symbolTableKey.timesInNewFile < 2 THEN symbolTableKey.timesInNewFile _ symbolTableKey.timesInNewFile + 1; END; ENDCASE => BEGIN IF symbolTableKey.timesInOldFile = 0 THEN BEGIN symbolTableKey.lineNumberInOldFile _ lineNumCurrentFile; symbolTableKey.posInOldFile _ posInCurrentFile; END ELSE EnterDuplicateRecord[symbolTableKey, lineNumCurrentFile, posInCurrentFile]; IF symbolTableKey.timesInOldFile < 2 THEN symbolTableKey.timesInOldFile _ symbolTableKey.timesInOldFile + 1; END; END; EnterDuplicateRecord: PROCEDURE [symbolTableKey: Wat.SymbolTableKey, lineNumCurrentFile: INTEGER, posInCurrentFile: INT] = BEGIN tempDupRec: REF DuplicateRecord _ DuplicateRecordZone.NEW[DuplicateRecord _ [lineNumCurrentFile, symbolTableKey, posInCurrentFile, duplicateList]]; duplicateList _ tempDupRec; END; EndOfFile: SIGNAL = CODE; ReadLineFromFile: PROCEDURE [InputFile: IO.Handle, localSwitchFileType: FileType, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [line: Rope.ROPE, char: CHARACTER, pos: INT] = BEGIN SkipCR: IO.CharProc = TRUSTED BEGIN RETURN[(char = IO.CR), TRUE]; END; 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; 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: Rope.ROPE, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [leadingChar: CHARACTER, skipLine: BOOLEAN] = BEGIN index: CARDINAL _ 0; length: CARDINAL = Rope.Length[line]; char: CHARACTER; 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]; 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: IO.Handle] RETURNS[nextChar: CHARACTER] = BEGIN nextChar _ char; WHILE ((nextChar = IO.SP) OR (nextChar = IO.TAB)) DO nextChar _ InputStream.GetChar[]; ENDLOOP; END; TerminalHoHoDisplayOutput: PROCEDURE[rope: Rope.ROPE, pause: BOOLEAN] = BEGIN UserExec.GetStreams[exec].out.PutF["%g*n", IO.rope[rope]]; IF pause THEN BEGIN UserExec.GetStreams[exec].out.PutRope["HIT RETURN TO EXIT: "]; [] _ UserExec.GetStreams[exec].in.GetChar[ ! IO.Signal => TRUSTED BEGIN IF ec = Rubout THEN BEGIN UserExec.GetStreams[exec].in.Reset[]; CONTINUE; END; END]; END; SIGNAL TerminatingSignal; -- what a crock. where did StopMesa go..... END; FinishUp: PROCEDURE[openDifFile, writeAndCloseDifFile: BOOLEAN, difMsg, displayMsg: Rope.ROPE, pause: BOOLEAN] = BEGIN IF openDifFile THEN OpenDifFileAndWriteHeader[]; IF writeAndCloseDifFile THEN BEGIN difFile.PutF["%g*n", IO.rope[difMsg]]; difFile.Close[FALSE]; END; TerminalHoHoDisplayOutput[displayMsg, pause]; END; IllegalFileName: ERROR = CODE; BuildDifFileName: PROCEDURE = BEGIN -- 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"]; END; OpenDifFileAndWriteHeader: PROCEDURE = BEGIN -- quick and dirty. difFile _ FileIO.Open[difFileName, overwrite, (IF switchOverwrite THEN none ELSE newOnly) ! FileIO.OpenFailed => TRUSTED BEGIN IF why = fileAlreadyExists THEN GOTO fileAlreadyExists; END]; difFile.PutF["*nWaterlily Version 1.13 %g*n*nDifferences between files %g and %g:*n*n", IO.time[], IO.rope[oldFileName], IO.rope[newFileName]]; EXITS fileAlreadyExists => TRUSTED BEGIN oldFile.Close[FALSE]; newFile.Close[FALSE]; TerminalHoHoDisplayOutput["Dif file already exists.", TRUE]; END; END; WorkingMsg: PROCEDURE = BEGIN UserExec.GetStreams[exec].out.PutChar['.]; END; ReadComDotCmOrPrompt: PROCEDURE = BEGIN newFileSeen, oldFileSeen: BOOLEAN _ FALSE; SyntaxError: ERROR = CODE; ProcessGlobalSwitches: PROCEDURE[char: CHARACTER] RETURNS[nextChar: CHARACTER] = BEGIN DO IF char = '/ THEN char _ event.commandLineStream.GetChar[]; SELECT char FROM 'b, 'B => BEGIN switchNewFileType _ bravo; switchOldFileType _ bravo; END; 't, 'T => BEGIN switchNewFileType _ tioga; switchOldFileType _ tioga; END; 'u, 'U => BEGIN switchNewFileType _ unform; switchOldFileType _ unform; END; 'o, 'O => switchOverwrite _ TRUE; 'p, 'P => switchPause _ TRUE; 'i, 'I => BEGIN switchIgnoreEmptyLinesNewFile _ TRUE; switchIgnoreEmptyLinesOldFile _ TRUE; END; '- => BEGIN char _ event.commandLineStream.GetChar[]; SELECT char FROM 'i, 'I => BEGIN switchIgnoreEmptyLinesNewFile _ FALSE; switchIgnoreEmptyLinesOldFile _ FALSE; END; 'p, 'P => BEGIN switchPause _ FALSE; END; 'o, 'O => switchOverwrite _ FALSE; ENDCASE => ERROR SyntaxError; END; IN ['0..'9] => BEGIN ropeHandle: IO.Handle _ IO.CreateOutputStreamToRope[]; number: INTEGER; tempIndex: INTEGER _ 0; WHILE char IN ['0..'9] DO IF tempIndex > 5 THEN ERROR SyntaxError; ropeHandle.PutChar[char]; tempIndex _ tempIndex + 1; char _ event.commandLineStream.GetChar[]; ENDLOOP; number _ IO.GetInt[IO.RIS[ropeHandle.GetOutputStreamRope[], NIL]]; ropeHandle.Close[]; SELECT char FROM 'm, 'M => BEGIN IF number <1 THEN ERROR SyntaxError; switchLinesForMatch _ number; END; 'c, 'C => switchLinesForContext _ number; ENDCASE => ERROR SyntaxError; END; ENDCASE => ERROR SyntaxError; SELECT (char _ event.commandLineStream.GetChar[]) FROM '-, 'b, 'B, 'i, 'I, 'p, 'P, 't, 'T, 'u, 'U, '/, IN ['0..'9] => LOOP; IO.SP, IO.TAB => BEGIN char _ SkipSpacesAndTabs[char, event.commandLineStream]; IF (char = '/) THEN LOOP; END; ENDCASE => NULL; RETURN[char]; ENDLOOP; END; ProcessLocalSwitches: PROCEDURE [switchFileType: POINTER TO FileType, switchIgnore: POINTER TO BOOLEAN] RETURNS[nextChar: CHARACTER] = BEGIN nextChar _ event.commandLineStream.GetChar[]; DO SELECT nextChar FROM 'b, 'B => switchFileType^ _ bravo; 't, 'T => switchFileType^ _ tioga; 'u, 'U => switchFileType^ _ unform; 'i, 'I => switchIgnore^ _ TRUE; '- => SELECT (nextChar _ event.commandLineStream.GetChar[]) FROM 'i, 'I => switchIgnore^ _ FALSE; ENDCASE => ERROR SyntaxError; ENDCASE => ERROR SyntaxError; SELECT (nextChar _ event.commandLineStream.GetChar[]) FROM '/ => nextChar _ event.commandLineStream.GetChar[]; '-, 'b, 'B, 'i, 'I, 't, 'T, 'u, 'U => NULL; ENDCASE => RETURN[nextChar]; ENDLOOP; END; OpenFile: PROCEDURE[tempFileName: Rope.ROPE, switchFileType: FileType] RETURNS[fileSeen: BOOLEAN, fileName: Rope.ROPE, file: IO.Handle] = BEGIN file _ FileIO.Open[tempFileName, read, oldOnly, , , switchFileType # tioga]; fileName _ Rope.Substr[tempFileName, 0, Rope.Length[tempFileName]]; fileSeen _ TRUE; END; -- may or may not be given a leading character, GetFileName: PROCEDURE [handle: IO.Handle, leadingChar: CHARACTER] RETURNS [tempFileName: Rope.ROPE, nextChar: CHARACTER] = BEGIN ropeHandle: IO.Handle _ IO.CreateOutputStreamToRope[]; IF LegalCharInFilename[leadingChar] THEN ropeHandle.PutChar[leadingChar]; WHILE LegalCharInFilename[nextChar _ handle.GetChar[]] DO ropeHandle.PutChar[nextChar]; ENDLOOP; tempFileName _ ropeHandle.GetOutputStreamRope[]; ropeHandle.Close[]; END; ProcessFilename: PROCEDURE[char: CHARACTER] RETURNS[nextChar: CHARACTER] = BEGIN tempFileName: Rope.ROPE _ NIL; IF newFileSeen AND oldFileSeen THEN ERROR SyntaxError; --guaranteed at least one legal char on entry. [tempFileName, nextChar] _ GetFileName[event.commandLineStream, char]; IF NOT oldFileSeen THEN BEGIN IF nextChar = '/ THEN nextChar _ ProcessLocalSwitches[@switchOldFileType, @switchIgnoreEmptyLinesOldFile]; [oldFileSeen, oldFileName, oldFile] _ OpenFile[tempFileName, switchOldFileType]; END ELSE BEGIN IF nextChar = '/ THEN nextChar _ ProcessLocalSwitches[@switchNewFileType, @switchIgnoreEmptyLinesNewFile]; [newFileSeen, newFileName, newFile] _ OpenFile[tempFileName, switchNewFileType]; END; nextChar _ SkipSpacesAndTabs[nextChar, event.commandLineStream]; END; PromptForFile: PROCEDURE[typeOfFile: Rope.ROPE, switchFileType: FileType] RETURNS [fileSeen: BOOLEAN, fileName: Rope.ROPE, file: IO.Handle] = BEGIN DO BEGIN tempFileName: Rope.ROPE _ NIL; nextChar: CHARACTER; UserExec.GetStreams[exec].out.PutF["%g file: ", IO.rope[typeOfFile]]; [tempFileName, nextChar] _ GetFileName[UserExec.GetStreams[exec].in, IO.SP]; IF nextChar # IO.CR THEN BEGIN UNTIL nextChar = IO.CR DO nextChar _ UserExec.GetStreams[exec].in.GetChar[]; ENDLOOP; GOTO err; END; IF Rope.Length[tempFileName] = 0 THEN GOTO err; [fileSeen, fileName, file] _ OpenFile[tempFileName, switchFileType ! FileIO.OpenFailed => TRUSTED BEGIN IF ((why = fileNotFound) OR (why = illegalFileName)) THEN GOTO err; END]; EXIT; EXITS err => UserExec.GetStreams[exec].out.PutF["%g*n", IO.rope["File not found or syntax error"]]; END; ENDLOOP; END; fatalMsg: Rope.ROPE _ NIL; BEGIN ENABLE BEGIN IO.EndOfStream, SyntaxError => BEGIN fatalMsg _ "Syntax error."; GOTO fatalError; END; FileIO.OpenFailed => TRUSTED BEGIN IF ((why = fileNotFound) OR (why = illegalFileName)) THEN BEGIN fatalMsg _ "File not found or syntax error."; GOTO fatalError; END; END; END; char: CHARACTER _ SkipSpacesAndTabs[IO.SP, event.commandLineStream]; IF (char = '/) THEN char _ ProcessGlobalSwitches[char]; IF LegalCharInFilename[char] THEN char _ ProcessFilename[char]; IF LegalCharInFilename[char] THEN char _ ProcessFilename[char]; char _ SkipSpacesAndTabs[char, event.commandLineStream]; IF ((char # IO.CR) AND (char # ';)) THEN ERROR SyntaxError; EXITS fatalError => BEGIN IF oldFileSeen THEN oldFile.Close[FALSE]; IF newFileSeen THEN newFile.Close[FALSE]; TerminalHoHoDisplayOutput[fatalMsg, TRUE]; END; END; -- PromptForFile does its own error checking since it can recover. BEGIN ENABLE IO.Signal => TRUSTED BEGIN IF ec = Rubout THEN BEGIN IF oldFileSeen THEN oldFile.Close[FALSE]; UserExec.GetStreams[exec].in.Reset[]; TerminalHoHoDisplayOutput["XXX ", FALSE]; END; END; IF NOT oldFileSeen THEN [oldFileSeen, oldFileName, oldFile] _ PromptForFile["Old", switchOldFileType]; IF NOT newFileSeen THEN [newFileSeen, newFileName, newFile] _ PromptForFile["New", switchNewFileType]; END; BuildDifFileName[! IllegalFileName => GOTO badDifFileName]; EXITS badDifFileName => BEGIN oldFile.Close[FALSE]; newFile.Close[FALSE]; TerminalHoHoDisplayOutput["Dif file would have illegal name.", TRUE]; END; END; -- start here. SymbolTableZone: ZONE = SafeStorage.NewZone[quantized, , ]; DuplicateRecordZone: ZONE = SafeStorage.NewZone[quantized, , ]; symbolTable: OST.Table; ok _ TRUE; msg _ NIL; OST.Initialize[SymbolTableZone.NEW[Wat.SymbolTableEntry], SymbolTableZone.NEW[Wat.SymbolTableEntry]]; symbolTable _ OST.CreateTable[header: SymbolTableZone.NEW[Wat.SymbolTableEntry]]; ReadComDotCmOrPrompt[]; UserExec.GetStreams[exec].out.PutRope["Comparing"]; WorkingMsg[]; BEGIN ScanDuplicateList: PROCEDURE[array: REF FileArray] = BEGIN UNTIL duplicateList = NIL DO array[duplicateList.lineNum] _ [symTableKey: duplicateList.symbolTableKey, posInThisFile: duplicateList.posInThisFile, lineNumInOtherFile: 0, typeOfPntr: symTable]; duplicateList _ duplicateList.nextDupRec; ENDLOOP; END; --Pass 1. nextLine: Rope.ROPE _ NIL; leadingChar: CHARACTER; DO [nextLine, leadingChar, posInNewFile] _ ReadLineFromFile[newFile, switchNewFileType, switchIgnoreEmptyLinesNewFile ! EndOfFile => GOTO donewithfile]; SetSymbolTableEntry[nextLine, new, totalLinesInNewFile, posInNewFile, leadingChar]; totalLinesInNewFile _ totalLinesInNewFile + 1; REPEAT donewithfile => BEGIN newFile.Close[FALSE]; IF totalLinesInNewFile = 0 THEN GOTO emptyfile; END; ENDLOOP; WorkingMsg[]; --Pass 1 and 1/2. newArray _ NEW[FileArray[totalLinesInNewFile]]; BEGIN Test: SymbolTableProcedure = TRUSTED BEGIN IF (symbolTableKey.timesInNewFile >= 1) THEN newArray[symbolTableKey.lineNumberInNewFile] _ [symTableKey: symbolTableKey, posInThisFile: symbolTableKey.posInNewFile, lineNumInOtherFile: 0, typeOfPntr: symTable]; RETURN[FALSE]; END; OST.EnumerateIncreasing[symbolTable, Test]; END; 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 => BEGIN oldFile.Close[FALSE]; IF totalLinesInOldFile = 0 THEN GOTO emptyfile; END; ENDLOOP; WorkingMsg[]; --Pass 2 and 1/2. oldArray _ NEW[FileArray[totalLinesInOldFile]]; BEGIN Test: SymbolTableProcedure = TRUSTED BEGIN IF (symbolTableKey.timesInOldFile >= 1) THEN oldArray[symbolTableKey.lineNumberInOldFile] _ [symTableKey: symbolTableKey, posInThisFile: symbolTableKey.posInOldFile, lineNumInOtherFile: 0, typeOfPntr: symTable]; RETURN[FALSE]; END; OST.EnumerateIncreasing[symbolTable, Test]; END; 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]; END; --Pass 3. BEGIN Test: SymbolTableProcedure = TRUSTED BEGIN IF ((symbolTableKey.timesInOldFile = 1) AND (symbolTableKey.timesInNewFile = 1)) THEN BEGIN newArray[symbolTableKey.lineNumberInNewFile].typeOfPntr _ lineNum; newArray[symbolTableKey.lineNumberInNewFile].lineNumInOtherFile _ symbolTableKey.lineNumberInOldFile; oldArray[symbolTableKey.lineNumberInOldFile].typeOfPntr _ lineNum; oldArray[symbolTableKey.lineNumberInOldFile].lineNumInOtherFile _ symbolTableKey.lineNumberInNewFile; END; RETURN[FALSE]; END; OST.EnumerateIncreasing[symbolTable, Test]; END; WorkingMsg[]; --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].symTableKey = oldArray[indexO].symTableKey)) 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; WorkingMsg[]; --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].symTableKey = oldArray[indexO].symTableKey)) 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; WorkingMsg[]; --Passes 6 and 7. BEGIN CancelMatch: PROCEDURE[array1, array2: REF FileArray, 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; WorkingMsg[]; --Pass 7. BEGIN DumpOutDiffAndMoveAhead: PROCEDURE = BEGIN index: INTEGER; LeadingNumber: PROCEDURE[char: CHARACTER, number: INT] = -- must be a better way to do this. BEGIN 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[") "]; END; IF NOT anyDifferencesSeen THEN BEGIN OpenDifFileAndWriteHeader[]; difFile.PutF["%g%g*n", IO.rope[Asterisks], IO.rope[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, 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; END; TryToResolveConflicts: PROCEDURE [array1, array2: REF FileArray, 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; 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 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; WorkingMsg[]; IF anyDifferencesSeen THEN BEGIN tempRope: Rope.ROPE _ Rope.Cat["Differences written on file ", difFileName, "."]; FinishUp[openDifFile: FALSE, writeAndCloseDifFile: TRUE, difMsg: " End of differences seen.", displayMsg: tempRope, pause: FALSE]; END ELSE FinishUp[openDifFile: FALSE, writeAndCloseDifFile: FALSE, difMsg: NIL, displayMsg: "No differences encountered.", pause: switchPause]; END; EXITS done => NULL; END; -- main body of module. ShortHelpMsg: Rope.ROPE = "Compares two source files."; LongHelpMsg: Rope.ROPE = " Waterlily compares two source files. This is the format of its command line: Waterlily filename1 filename2 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: Waterlily NewPkgImpl.mesa [Indigo]Pkg>PkgImpl.mesa Waterlily /b Foo.bravo Foo2.bravo/-i Wate /6m OldMemo.Bravo/b Memo.tioga Waterlily (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. "; UserExec.RegisterCommand["Waterlily", WaterlilyProc, ShortHelpMsg, LongHelpMsg, ]; 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 .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. ÊÒ˜JšÃœÏcœœ&œÏk œžœžœ žœžœÇžœ žœžœ*žœežœ%žœ%žœ"žœUžœ&Ðbl œžœžœ žœžœPžœ¸œÏnœžœžœ  œžœž œGžœžœžœžœ žœžœžœžœžœ-œžœžœžœžœžœžœžœ žœžœžœ+žœžœ>žœžœFžœžœžœžœžœžœžœžœžœžœžœžœžœžœ4&œ žœ—žœžœ "œžœ žœžœžœžœžœžœžœžœ  œžœžœž œ+žœžœžœžœžœžœ žœ;žœžœ œž œžœ0žœžœž œ žœ1žœžœ"žœ žœžœ0žœ;žœžœžœ&žœžœžœ+žœ#žœžœ+žœ#žœžœÆžœIžœ žœžœ žœžœžœBžœžœÞžœ žœ‹žœBžœ‚žœžœžœžœ@žœžœØžœžœ‡žœ@žœ~žœžœ  œž œ>žœžœ žœžœ'žœŒžœžœžœ  œž œ žœFžœžœ žœ ž œžœ žœžœžœžœžœžœžœžœžœžœcœ,œžœžœ žœHžœŽžœžœ žœžœžœžœ JœBœ œž œ žœ žœžœž œžœ žœžœžœ"ž œžœžœ0žœ žœžœžœ žœžœžœžœ-žœ žœžœ žœœžœžœžœ-žœ  œž œž œžœžœ žœžœžœ žœ žœ žœ=žœžœ žœžœžœ žœ  œž œž œžœ žœ ž œ žœžœžœžœžœ žœžœ žœ#žœžœ œž œ žœ žœ žœ2žœžœžœžœžœ žœžœžœ$žœžœ'žœžœžœžœžœ-œžœ œž œ$žœžœ žœ žœžœ žœ$žœžœžœ$žœ,žœžœ<žœžœžœ  œž œ žœœžœ7žœžœžœžœAžœ žœ žœžœžœ,žœžœžœUžœEžœžœžœ_žœ œž œ žœœ4žœžœžœ*ž œžœžœžœžœŸžœ žœžœžœžœžœžœ9žœžœž œ  œž œ žœ8žœ  œž œ žœ!žœžœžœžœ œž œž œžœž œ žœ žœ žœ žœ4žœžœžœMžœžœMžœžœežœ*žœ&žœžœ!žœ6žœžœžœ=žœžœ žœ!žœBžœžœ!žœžœžœ3žœžœžœ žœžœ žœ žœ'žœ@žœ(žœ!žœžœ(žœžœžœžœåžœDžœžœžœ#žœNžœžœ)žœ)žœ žœžœ|žœežœžœ)žœ žœžœžœ,žœ=žœ žœžœžœžœžœžœtžœ žœžœžœ žœžœ žœžœ žœ  œž œžœžœ žœžœžœžœ ž œ žœAžœ žœ žœ²žœžœ0žœ0žœžœžœžœžœžœ0žœsžœ žœžœžœ žœ œž œžœ#žœ žœžœžœžœÄžœ žœ0œ  œž œ žœž œžœžœ ž œ žœžœ žœ&žœ"žœ+žœ>žœžœbžœ œž œž œžœ ž œ žœžœžœ žœ žœ žœžœ/œežœžœžœžœžœžœ…žœ žœžœžœžœ…žœUžœ  œž œžœ#žœ žœžœžœžœ žœ žœžœžœž œ;žœbžœžœ žœ žœžœ žœžœžœ žœžœžœKžœžœžœ žœžœžœužœžœžœžœžœžœžœ žœ žœDžœ3žœ žœ žœ(žœžœžœžœžœžœ.žœžœ žœ2žœžœžœžœžœžœ@žœ žœžœžœž œžœžœ"žœ žœ+žœžœ%žœžœdžœ žœžœžœžœžœžœžœžœ žœžœžœ žœžœAžœžœžœCœžœžœžœ žœžœžœžœžœžœ žœžœužœžœžœžœžœžœbžœžœžœbžœ.žœžœžœžœžœfžœžœžœ œžœ?žœ7žœžœ žœ žœžœ.žœ*žœ+žœˆžœ œž œžœžœžœžœ žœƒžœžœ œžœžœž œžœŒžœ™ž œžœžœžœžœžœžœžœœžœ*žœ'žœžœžœ/žœÆžœžœ žœžœ,žœE œžœŒžœ™ž œžœžœžœžœžœžœžœœžœ)žœ'žœžœžœ/žœÆžœžœ žœžœ,žœ>žœ%žœ?žœížœ œžœ'žœžœžœ&žœ6žœžœ»žœžœžœ žœ žœ,žœ œžœ$žœžœ2žœžœržœ!žœAžœžœ+žœ<žœgžœžœ“žœžœžœ*žœAžœHžœcžœžœlžœžœžœžœ žœžœ œ(žœžœžœ2žœžœdžœžœ!žœžœ+žœ=žœgžœžœžœžœžœ*žœDžœKžœjžœžœlžœžœžœžœ žœžœœžœ  œž œžœ-žœ žœžœ0žœžœ"žœ\žœžœ žœžœ œžœžœžœžœ.žœžœ8žœžœ žœLžœ!žœžœžœžœ/žœQžœržœžœ$žœ žœ œžœ œž œ žœžœ  œž œž œ žœ$œ žœžœ$žœžœ-žœžœžœ7žœžœžœžœžœžœ8žœžœžœžœžœCžœžœ6žœžœžœ"žœ+žœžœYžœ žœžœ8žœ?žœ<žœžœžœQžœ*žœžœžœ0žœ(žœQžœ*žœžœžœžœžœQžœ*žœžœžœ0žœ(žœQžœ*žœžœžœ:žœ žœ:žœžœbžœžœ'žœ|žœ  œž œžœ žœžœžœ žœžœIžœžœ žœ6žœžœ6žœžœžœOžœ}žœžœ}žœžœ$žœžœ žœžœžœ žœžœžœžœ(žœžœ!žœžœ žœžœžœ'žœ!žœ žœ-žœ-žœ0žœ;žœ=žœBžœžœ!žœ'žœžœ*žœ@žœžœžœ*žœHžœžœžœ1žœ_žœžœ,žœžœvžœužœžœ:žœnžœlžœ$žœžœžœžœžœ5žœžœ žœžœGžœžœžœ$žœ*žœžœžœžœžœžœnžœžœRžœžœ žœžœ(žœ žœYžœ žœ žœ žœœžœ3žœÒ žœsžœ"žœžœužœØžœ”žœ ˜‚«—…—•„¥\