<> <> <> <> 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; <> 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 = { <> 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: ROPE _ NIL; oldFileName: ROPE _ NIL; difFileName: ROPE _ NIL; Asterisks: ROPE = "**************************************"; <> FileType: TYPE = {tioga, bravo, unform}; switchNewFileType, switchOldFileType: FileType _ tioga; switchIgnoreEmptyLinesNewFile, switchIgnoreEmptyLinesOldFile: BOOL _ TRUE; <> switchLinesForMatch: INTEGER _ 3; switchLinesForContext: INTEGER _ 1; anyDifferencesSeen: BOOL _ FALSE; 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; }; <> symbolTable: WaterlilyOrderedSymbolTable.Table; result _ NIL; msg _ NIL; WaterlilyOrderedSymbolTable.Initialize[ NEW[WaterlilyDefs.SymbolTableEntry], NEW[WaterlilyDefs.SymbolTableEntry]]; symbolTable _ WaterlilyOrderedSymbolTable.CreateTable[header: NEW[WaterlilyDefs.SymbolTableEntry]]; <> { args: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ "Syntax error."; GO TO fatalError} ]; fileCount: NAT _ 0; defaultFileType: FileType _ tioga; ignoreEmptyLines: BOOL _ TRUE; FOR i: NAT IN [1..args.argc) DO arg: ROPE = args[i]; sense: BOOL _ TRUE; IF Rope.Match["-*", arg] THEN { <> 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 { <> 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[]; }; <> { 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; }; <> 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]; newFile _ NIL; IF totalLinesInNewFile = 0 THEN GOTO emptyfile; }; ENDLOOP; WorkingMsg[]; <> { 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[]; }; <> 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[]; <> { 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."]; }; <> { 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[]; }; <> { 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[]; }; <> { 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[]; }; <> { 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; }; <> 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[]; <> { DumpOutDiffAndMoveAhead: PROC = { index: INT; LeadingNumber: PROC[char: CHAR, number: INT] = { <> 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: 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 { 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; <> 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 .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.