--WaterlilyImpl.mesa -- Last edited by: -- Kolling on December 12, 1983 12:15 pm DIRECTORY Commander USING[CommandProc, Register], EditedStream USING[Rubout], FS USING[defaultStreamOptions, FileInfo, StreamOpen, StreamOptions, Error], IO USING[char, Close, CR, EndOf, EndOfStream, GetChar, GetIndex, GetInt, GetLineRope, int, Put, PutChar, PutF, PutRope, Reset, RIS, rope, RopeFromROS, ROS, SP, STREAM, TAB, time], OrderedSymbolTable USING[CreateTable, EnumerateIncreasing, Initialize, Insert, Lookup, Table], Rope USING[Cat, Fetch, Find, FromChar, Length, ROPE, Substr], WaterlilyDefs USING[SymbolTableEntry, SymbolTableKey]; WaterlilyImpl: CEDAR PROGRAM IMPORTS Commander, EditedStream, FS, IO, OST: OrderedSymbolTable, Rope = BEGIN OPEN Wat: WaterlilyDefs; --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; -- Commander.CommandProc TYPE = PROC [cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL]; WaterlilyProc: Commander.CommandProc = 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.STREAM; oldFile: IO.STREAM; difFile: IO.STREAM; 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; cmdLineStream: IO.STREAM _ IO.RIS[cmd.commandLine]; -- simplest conversion to 5.0. 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 _ 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 _ NEW[DuplicateRecord _ [lineNumCurrentFile, symbolTableKey, posInCurrentFile, duplicateList]]; duplicateList _ tempDupRec; END; EndOfFile: SIGNAL = CODE; ReadLineFromFile: PROCEDURE [InputFile: IO.STREAM, localSwitchFileType: FileType, localSwitchIgnoreEmpties: BOOLEAN] RETURNS [line: Rope.ROPE, char: CHARACTER, pos: INT] = BEGIN DO skipLine: BOOLEAN; index: INT; IF InputFile.EndOf[] THEN SIGNAL EndOfFile; pos _ InputFile.GetIndex[]; line _ InputFile.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; 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.STREAM] 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 cmd.out.PutF["%g\n", IO.rope[rope]]; IF pause THEN BEGIN cmd.out.PutRope["HIT RETURN TO EXIT: "]; [] _ cmd.in.GetChar[ ! EditedStream.Rubout => BEGIN cmd.in.Reset[]; CONTINUE; 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. index: INT _ Rope.Find[oldFileName, Rope.FromChar['!], 0, TRUE]; length: INT _ (IF index = -1 THEN Rope.Length[oldFileName] ELSE index); firstPosOfFileName: INT _ 0; DO index _ Rope.Find[oldFileName, Rope.FromChar['>], firstPosOfFileName, TRUE]; IF index = -1 THEN EXIT; IF (firstPosOfFileName _ index + 1) >= length THEN ERROR IllegalFileName; ENDLOOP; -- have firstPosOfFileName. index _ Rope.Find[oldFileName, Rope.FromChar['.], firstPosOfFileName, TRUE]; IF index # -1 THEN BEGIN IF index >= length THEN ERROR IllegalFileName; length _ index; END; difFileName _ Rope.Cat[Rope.Substr[oldFileName, firstPosOfFileName, (length - firstPosOfFileName)], ".dif"]; END; OpenDifFileAndWriteHeader: PROCEDURE = BEGIN -- quick and dirty. IF (NOT switchOverwrite) THEN BEGIN[] _ FS.FileInfo[difFileName ! FS.Error => IF ((error.group = user) AND (error.code = $unknownFile)) THEN GOTO noSuchFile;]; oldFile.Close[FALSE]; newFile.Close[FALSE]; TerminalHoHoDisplayOutput["Dif file already exists.", TRUE]; RETURN; EXITS noSuchFile => NULL; -- this is what we want. END; difFile _ FS.StreamOpen[difFileName, $create]; difFile.PutF["\nWaterlily Version 1.14 %g\n\nDifferences between files %g and %g:\n\n", IO.time[], IO.rope[oldFileName], IO.rope[newFileName]]; END; WorkingMsg: PROCEDURE = BEGIN cmd.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 _ cmdLineStream.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 _ cmdLineStream.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.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 _ cmdLineStream.GetChar[]; ENDLOOP; number _ IO.GetInt[IO.RIS[ropeHandle.RopeFromROS[], 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 _ cmdLineStream.GetChar[]) FROM '-, 'b, 'B, 'i, 'I, 'p, 'P, 't, 'T, 'u, 'U, '/, IN ['0..'9] => LOOP; IO.SP, IO.TAB => BEGIN char _ SkipSpacesAndTabs[char, cmdLineStream]; IF (char = '/) THEN LOOP; END; ENDCASE => NULL; RETURN[char]; ENDLOOP; END; ProcessLocalSwitches: PROCEDURE [switchFileType: POINTER TO FileType, switchIgnore: POINTER TO BOOLEAN] RETURNS[nextChar: CHARACTER] = TRUSTED BEGIN nextChar _ cmdLineStream.GetChar[]; DO SELECT nextChar FROM 'b, 'B => switchFileType^ _ bravo; 't, 'T => switchFileType^ _ tioga; 'u, 'U => switchFileType^ _ unform; 'i, 'I => switchIgnore^ _ TRUE; '- => SELECT (nextChar _ cmdLineStream.GetChar[]) FROM 'i, 'I => switchIgnore^ _ FALSE; ENDCASE => ERROR SyntaxError; ENDCASE => ERROR SyntaxError; SELECT (nextChar _ cmdLineStream.GetChar[]) FROM '/ => nextChar _ cmdLineStream.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.STREAM] = BEGIN streamOptions: FS.StreamOptions _ FS.defaultStreamOptions; streamOptions[tiogaRead] _ (switchFileType = tioga); file _ FS.StreamOpen[fileName: tempFileName, accessOptions: $read, streamOptions: streamOptions]; fileName _ Rope.Substr[tempFileName, 0, Rope.Length[tempFileName]]; fileSeen _ TRUE; END; -- may or may not be given a leading character, GetFileName: PROCEDURE [handle: IO.STREAM, leadingChar: CHARACTER] RETURNS [tempFileName: Rope.ROPE, nextChar: CHARACTER] = BEGIN ropeHandle: IO.STREAM _ IO.ROS[]; IF LegalCharInFilename[leadingChar] THEN ropeHandle.PutChar[leadingChar]; WHILE LegalCharInFilename[nextChar _ handle.GetChar[]] DO ropeHandle.PutChar[nextChar]; ENDLOOP; tempFileName _ ropeHandle.RopeFromROS[]; ropeHandle.Close[]; END; ProcessFilename: PROCEDURE[char: CHARACTER] RETURNS[nextChar: CHARACTER] = TRUSTED BEGIN tempFileName: Rope.ROPE _ NIL; IF newFileSeen AND oldFileSeen THEN ERROR SyntaxError; --guaranteed at least one legal char on entry. [tempFileName, nextChar] _ GetFileName[cmdLineStream, 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, cmdLineStream]; END; PromptForFile: PROCEDURE[typeOfFile: Rope.ROPE, switchFileType: FileType] RETURNS [fileSeen: BOOLEAN, fileName: Rope.ROPE, file: IO.STREAM] = BEGIN DO BEGIN tempFileName: Rope.ROPE _ NIL; nextChar: CHARACTER; cmd.out.PutF["%g file: ", IO.rope[typeOfFile]]; [tempFileName, nextChar] _ GetFileName[cmd.in, IO.SP]; IF nextChar # IO.CR THEN BEGIN UNTIL nextChar = IO.CR DO nextChar _ cmd.in.GetChar[]; ENDLOOP; GOTO err; END; IF Rope.Length[tempFileName] = 0 THEN GOTO err; [fileSeen, fileName, file] _ OpenFile[tempFileName, switchFileType ! FS.Error => TRUSTED BEGIN IF ((error.code = $unknownFile) OR (error.code = $illegalName)) THEN GOTO err; END]; EXIT; EXITS err => cmd.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; FS.Error => BEGIN IF ((error.code = $unknownFile) OR (error.code = $illegalName)) THEN BEGIN fatalMsg _ "File not found or syntax error."; GOTO fatalError; END; END; END; char: CHARACTER _ SkipSpacesAndTabs[IO.SP, cmdLineStream]; IF (char = '/) THEN char _ ProcessGlobalSwitches[char]; IF LegalCharInFilename[char] THEN char _ ProcessFilename[char]; IF LegalCharInFilename[char] THEN char _ ProcessFilename[char]; char _ SkipSpacesAndTabs[char, cmdLineStream]; 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 EditedStream.Rubout => BEGIN IF oldFileSeen THEN oldFile.Close[FALSE]; cmd.in.Reset[]; TerminalHoHoDisplayOutput["XXX ", FALSE]; 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. symbolTable: OST.Table; result _ NIL; msg _ NIL; OST.Initialize[NEW[Wat.SymbolTableEntry], NEW[Wat.SymbolTableEntry]]; symbolTable _ OST.CreateTable[header: NEW[Wat.SymbolTableEntry]]; ReadComDotCmOrPrompt[]; cmd.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. -- UserExec types up to first CR as "shortHelpMsg", all for "longHelpMsg". 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. "; Commander.Register[key: "Waterlily", proc: WaterlilyProc, doc: LongHelpMsg, clientData: ]; 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. Change: October 12, 1983 2:56 pm: Version 1.14. conversion to 5.0. Ê­˜Jš°œÏcœœ*œÏk œžœ4žœžœJžœ žœžœÓžœežœ%žœ'žœ&Ðbl œž œžœDžœžœ¸œÏnœžœžœÐck¡¡ ¡¡ ¡¡œ  œžœžœžœ-œžœžœžœžœžœžœžœ žœžœžœ+žœžœ>žœžœFžœžœžœžœž œž œž œžœžœžœžœžœžœžœ4&œ žœ—žœžœ "œžœ žœžœžœžœžœžœžœžœ>œ œžœžœž œ+žœžœžœžœžœžœ žœ;žœžœ œž œžœ0žœžœž œ žœ1žœžœ"žœ žœžœ žœ;žœžœžœ&žœžœžœ+žœ#žœžœ+žœ#žœžœÆžœIžœ žœžœ žœžœžœBžœžœÞžœ žœ‹žœBžœ‚žœžœžœžœ@žœžœØžœžœ‡žœ@žœ~žœžœ  œž œ>žœžœ žœžœžœŒžœžœžœ  œž œ ž œ?žœžœ žœ ž œžœ žœžœžœžœžœžœžœZžœžœ žœHžœŽžœžœ žœžœžœžœ JœBœ œž œ žœ žœžœž œžœ žœžœžœ"ž œžœžœ0žœ žœžœžœ žœžœžœžœ-žœ žœžœ žœœžœžœžœ-žœ  œž œž œžœžœ žœžœžœ žœ žœ žœ=žœžœ žœžœžœ žœ  œž œž œž œžœ ž œ žœžœžœžœžœ žœžœ žœ#žœžœ œž œ žœ žœ žœžœžœžœžœhžœžœžœžœžœ-œžœ œž œ$žœžœ žœ žœžœ žœ$žœžœžœ$žœ,žœžœ<žœžœžœ  œž œ žœœ žœ0žœžœžœ žœžœ#žœ žœMžœ žœ žœžœžœ,žœžœžœœMžœž œ žœžœž œžœžœžœBžœ„žœ œž œ žœž œžœžœž œ>žœžœ/ž œžœžœžœ!žœIžœ žœžœ žžœžœÓžœ žœžœž œ  œž œ žœ"žœ  œž œ žœ!žœžœžœžœ œž œž œžœž œ žœ žœ žœ žœ*žœžœžœMžœžœMžœžœežœ*žœ&žœžœ!žœ6žœžœžœ3žœžœ žœ!žœBžœžœ!žœžœžœ3žœžœžœ žœžœ žœ ž œžœ+žœ(žœ!žœžœ(žœžœžœžœÛžœDžœžœžœžœNžœžœ)žœ)žœ žœžœ|žœežœžœ)žœ žœžœžœ"žœ=žœ žœžœžœžœžœžœMžœ žœžœžœ žœžœ žœžœ žœ  œž œžœžœ žœžœžœžœ ž œ ž œ7žœ žœ žœ²žœžœ&žœ0žœžœžœžœžœžœ&žœižœ žœžœžœ žœ œž œžœ#žœ žœžœž œ žœxžœžœàžœ žœ0œ  œž œ ž œž œžœžœ ž œ žœž œžœžœ"žœ+žœ>žœžœZžœ œž œž œžœ ž œ ž œžœžœ žœ žœ žœžœ/œOžœžœžœžœžœžœ…žœ žœžœžœžœ…žœKžœ  œž œžœ#žœ žœžœž œ žœ žœ žœžœžœž œ%žœLžœžœ žœ žœžœ žœžœžœ žœžœžœ5žœžœžœ žœžœžœlžœžœžœžœžœžœžœ žœ žœžœ3žœ žœ žœ(žœžœžœžœžœžœ.ž œžœ žœ-ž œžœžœ/ž œžœ@žœ%ž œž œžœž œžœžœžœ žœ+žœžœ%žœžœZžœ žœžœžœžœžœžœžœžœ žœžœžœ žœžœAžœžœžœCœžœžœž'œžœ žœžœkžœžœžœžœžœbžœžœžœbžœ.žœžœžœžœžœfžœžœžœ œžœžœ žœ žœ žœžœ*žœžœržœ œž œžœžœžœžœ žœƒžœžœ œžœžœž œžœŒžœ™ž œžœžœžœžœžœžœžœœžœ*žœ'žœžœžœ/žœÆžœžœ žœžœ,žœ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žœ žœ žœ žœœJœžœÚ žœsžœ"žœžœužœØžœ”žœæ˜ò©—…—”ô¤§