-- file: Compare.mesa -- Edited by: Loretta, October 20, 1980 3:29 PM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [BytesPerPage, BytesPerWord, PageSize], BcdDefs USING [Base, BCD, MTRecord, VersionStamp], BcdOps USING [MTHandle, SGHandle], ImageDefs USING [AbortMesa, MakeImage, StopMesa], IODefs USING [CR, ESC, GetInputStream, GetOutputStream, NumberFormat, ReadChar, ReadID, SP, WriteChar, WriteDecimal, WriteLine, WriteNumber, WriteOctal, WriteString], MiscDefs USING [CallDebugger], OsStaticDefs USING [OsStatics], SegmentDefs USING [Read], Stream USING [Block, Delete, EndOfStream, Handle, GetBlock], StreamDefs USING [DisplayHandle, NewByteStream, StreamError, StreamHandle], String USING [AppendChar, AppendString, StringToDecimal], StringDefs USING [BcplToMesaString, MesaToBcplString], Storage USING [Words, FreeString, FreeWords], STP USING [Close, Create, CreateFileStream, CreateRemoteStream, Destroy, Error, ErrorCode, Handle, Login, NextFileName, Open, SetDirectory], Time USING [Append, Unpack]; Compare: PROGRAM IMPORTS IODefs, MiscDefs, Storage, STP, Stream, StreamDefs, StringDefs, String, Time, ImageDefs SHARES String = BEGIN OPEN AltoDefs, IODefs, String; format: NumberFormat = [base:8,zerofill:FALSE,unsigned:TRUE,columns:8]; byteformat: NumberFormat = [base:8,zerofill:FALSE,unsigned:TRUE,columns:3]; pair: TYPE = RECORD[left,right: [0..377B]]; STPuser: STP.Handle ← NIL; name: STRING ← [40]; switches: STRING ← [5]; version: BcdDefs.VersionStamp; differences: BOOLEAN ← FALSE; MaxDiffs: CARDINAL ← 20; difcount: CARDINAL; BufSize: CARDINAL = 20*PageSize; buffer1: POINTER = Storage.Words[BufSize]; buffer2: POINTER = Storage.Words[BufSize]; header: POINTER TO BcdDefs.BCD = Storage.Words[SIZE[BcdDefs.BCD]]; -- Utilities CompareStreams: PROCEDURE [stream1,stream2: Stream.Handle, length1, length2: CARDINAL] = BEGIN bufcount1,bufcount2: CARDINAL ← 0; code1,code2: POINTER TO ARRAY[0..BufSize) OF WORD; w: pair; i: CARDINAL; BuffersFilled: PROCEDURE [f1,f2: Stream.Handle,l1,l2: CARDINAL] RETURNS [BOOLEAN] = BEGIN IF MIN[l1,l2] <= 0 THEN RETURN[FALSE]; SELECT bufcount1 FROM = bufcount2 => BEGIN code1 ← buffer1; code2 ← buffer2; [bufcount1,,] ← Stream.GetBlock[sH: f1, block: [blockPointer: buffer1, startIndex: 0, stopIndexPlusOne: l1*BytesPerWord]]; [bufcount2,,] ← Stream.GetBlock[sH: f2, block: [blockPointer: buffer2, startIndex: 0, stopIndexPlusOne: l2*BytesPerWord]]; bufcount1 ← bufcount1/BytesPerWord; bufcount2 ← bufcount2/BytesPerWord; IF MIN[bufcount1,bufcount2] <= 0 THEN RETURN [FALSE] ELSE RETURN[TRUE]; END; > bufcount2 => BEGIN code1 ← buffer1; code2 ← code2+bufcount1; --change array origin bufcount2 ← bufcount2 - bufcount1; [bufcount1,,] ← Stream.GetBlock[sH: f1, block: [blockPointer: buffer1, startIndex: 0, stopIndexPlusOne: l1*BytesPerWord]]; bufcount1 ← bufcount1/BytesPerWord; IF bufcount1 <= 0 THEN RETURN [FALSE] ELSE RETURN[TRUE]; END; < bufcount2 => BEGIN code1 ← code1+bufcount2; --change array origin code2 ← buffer2; bufcount1 ← bufcount1 - bufcount2; [bufcount2,,] ← Stream.GetBlock[sH: f2, block: [blockPointer: buffer2, startIndex: 0, stopIndexPlusOne: l2*BytesPerWord]]; bufcount2 ← bufcount2/BytesPerWord; IF bufcount2 <= 0 THEN RETURN[FALSE] ELSE RETURN[TRUE]; END; ENDCASE; RETURN[FALSE]; END; -- body of CompareStreams difcount ← 0; bufcount1←bufcount2←0; WHILE BuffersFilled[stream1,stream2,length1,length2] DO length1 ← length1 - bufcount1; length2 ← length2 - bufcount2; FOR i IN [0..MIN[bufcount1,bufcount2]) DO IF code1[i] # code2[i] THEN BEGIN WriteNumber[i,format]; WriteNumber[code1[i],format]; WriteChar['[]; w ← LOOPHOLE[code1[i], pair]; WriteNumber[w.left, byteformat]; WriteChar[',]; WriteNumber[w.right, byteformat]; WriteChar[']]; WriteNumber[code2[i],format]; WriteChar['[]; w ← LOOPHOLE[code2[i], pair]; WriteNumber[w.left, byteformat]; WriteChar[',]; WriteNumber[w.right, byteformat]; WriteChar[']]; WriteChar[CR]; IF difcount = MaxDiffs THEN BEGIN WriteLine["Too many differences encountered. Quitting."]; GOTO quit END; difcount ← difcount + 1; differences ← TRUE; END; ENDLOOP; REPEAT quit => NULL; ENDLOOP; WriteChar[CR]; IF difcount = MaxDiffs THEN WriteString["More than "]; WriteDecimal[difcount]; WriteLine[" differences"]; END; Config: SIGNAL = CODE; NoCode: SIGNAL = CODE; FindCode: PROCEDURE [stream:Stream.Handle, buf: POINTER] RETURNS [ codeCount: CARDINAL, dateStamp: BcdDefs.VersionStamp] = -- positions stream to start of code --FindCode returns size in words (codeCount) BEGIN OPEN BcdDefs; ENABLE UNWIND => NULL; streamPos: CARDINAL; sgPointer: BcdDefs.Base; mtPos, sgPos: CARDINAL; codeBase: CARDINAL; mOffset: CARDINAL; mth: BcdOps.MTHandle; sgh: BcdOps.SGHandle; AdvanceStream: PROCEDURE [newPos: CARDINAL] = -- positions stream to byte position newPos BEGIN incr: CARDINAL; WHILE newPos > streamPos DO [incr,,] ← Stream.GetBlock[stream, Stream.Block[buf, 0, MIN[BufSize, newPos - streamPos]]]; streamPos ← streamPos + incr; ENDLOOP; END; [streamPos,,] ← Stream.GetBlock[stream, Stream.Block[header,0,SIZE[BCD]*BytesPerWord]]; --read header -- get module table, which contains index of code segment, -- starting offset in segment (in words) -- and length of code (in bytes) IF header.nConfigs > 0 THEN SIGNAL Config; mtPos ← header.mtOffset*BytesPerWord; sgPos ← header.sgOffset*BytesPerWord; dateStamp ← header.version; IF mtPos < sgPos THEN -- read module table first, record fields, then overwrite with segment table BEGIN AdvanceStream[mtPos]; mth ← LOOPHOLE[buf, BcdOps.MTHandle]; AdvanceStream[mtPos + LOOPHOLE[header.mtLimit,CARDINAL]*BytesPerWord]; mOffset ← mth.code.offset; codeCount ← mth.code.length; sgPos ← sgPos + LOOPHOLE[mth.code.sgi,CARDINAL]*BytesPerWord; AdvanceStream[sgPos]; sgh ← LOOPHOLE[buf, BcdOps.SGHandle]; AdvanceStream[sgPos + LOOPHOLE[header.sgLimit,CARDINAL]*BytesPerWord]; END ELSE -- must read segment table before module table, but don't know which part of --segment table! store segment table in first part of buffer, advance buffer -- pointer to read module table behind it, access both at once BEGIN AdvanceStream[sgPos]; sgPointer ← LOOPHOLE[buf,BcdDefs.Base]; AdvanceStream[sgPos + LOOPHOLE[header.sgLimit,CARDINAL]*BytesPerWord]; buf ← LOOPHOLE[LOOPHOLE[buf,CARDINAL] + LOOPHOLE[header.sgLimit,CARDINAL],POINTER]; AdvanceStream[mtPos]; mth ← LOOPHOLE[buf, BcdOps.MTHandle]; AdvanceStream[mtPos + LOOPHOLE[header.mtLimit,CARDINAL]*BytesPerWord]; sgh ← @sgPointer[mth.code.sgi]; mOffset ← mth.code.offset; codeCount ← mth.code.length; END; IF codeCount = 0 THEN SIGNAL NoCode; -- compute starting address of code (StreamPosition) codeBase ← (sgh.base-1)*BytesPerPage + mOffset*BytesPerWord; AdvanceStream[codeBase]; -- round codeCount up to nearest work boundary and convert to word count IF codeCount MOD 2 # 0 THEN codeCount ← codeCount + 1; codeCount ← codeCount/BytesPerWord; RETURN END; ExhaustStream: PROCEDURE[stream: Stream.Handle, buf: POINTER] = BEGIN DO ENABLE Stream.EndOfStream => EXIT; [,,] ← Stream.GetBlock[stream, Stream.Block[buf, 0, BufSize*BytesPerWord]]; ENDLOOP; END; CheckStream: PROCEDURE[stream: Stream.Handle] = BEGIN ENABLE UNWIND => Stream.Delete[stream]; remoteFileName: STRING ← NIL; remoteFileName ← STP.NextFileName[stream]; IF remoteFileName # NIL THEN Storage.FreeString[remoteFileName] ELSE SIGNAL STP.Error[noSuchFile,"File not Found."]; END; ForceBCDExtension: PROCEDURE[string: STRING] = BEGIN i: CARDINAL ← 0; FOR i IN [0..name.length) DO IF name[i] = '. THEN BEGIN string.length ← i; EXIT END; ENDLOOP; String.AppendString[string, ".bcd"]; END; InitSTP: PROCEDURE [server: STRING] = BEGIN herald: STRING ← NIL; user: STRING ← [40]; password: STRING ← [40]; STPuser ← STP.Create[]; StringDefs.BcplToMesaString[OsStaticDefs.OsStatics.UserName, user]; StringDefs.BcplToMesaString[OsStaticDefs.OsStatics.UserPassword, password]; herald ← STP.Open[STPuser,server ! STP.Error => BEGIN SELECT code FROM noSuchHost => WriteLine["No such Host."L]; connectionTimedOut => WriteLine["Timeout."L]; connectionRejected => WriteLine["Rejected."L]; ENDCASE => WriteLine["Other (unspecified) error."L]; END]; WriteLine[herald]; Storage.FreeString[herald]; STP.Login[STPuser, user, password !STP.Error => CONTINUE]; END; PrintVersion: PROCEDURE [stamp: BcdDefs.VersionStamp ] = BEGIN OPEN Time; tmp: STRING ← [20]; WriteString[" compiled by "]; WriteOctal[stamp.net]; WriteChar['#]; WriteOctal[stamp.host]; WriteString["# on "]; Append[tmp,Unpack[stamp.time]]; WriteString[tmp]; END; ProcessSwitches: PROCEDURE = BEGIN i: CARDINAL; FOR i IN [0..switches.length) DO SELECT switches[i] FROM 'd,'D => IF name.length = 0 THEN MiscDefs.CallDebugger["Called from Code Compare"] ELSE BEGIN STP.SetDirectory[STPuser,name]; IODefs.WriteString[" Directory "]; IODefs.WriteLine[name]; END; 'm,'M => MaxDiffs ← StringToDecimal[name]; ENDCASE; ENDLOOP; END; TrimFileName: PROCEDURE[string: STRING] = BEGIN i,j: CARDINAL; IF string[0] = '< THEN BEGIN FOR i DECREASING IN [1..string.length) DO IF string[i] = '> THEN BEGIN i ← i+1; FOR j IN [i..string.length) DO string[j-i] ← string[j] ENDLOOP; string.length ← string.length-i; RETURN; END; ENDLOOP; string.length ← 0 END; END; Login: PROCEDURE = BEGIN CR: CHARACTER = IODefs.CR; ESC: CHARACTER = IODefs.ESC; SP: CHARACTER = IODefs.SP; user: STRING = [40]; password: STRING = [40]; c: CHARACTER; StringDefs.BcplToMesaString[OsStaticDefs.OsStatics.UserName,user]; WriteString["UserName: "L]; WriteString[user]; c ← IODefs.ReadChar[]; IF c#CR AND c#SP AND c#ESC THEN BEGIN CollectRestOfNewString[user,c]; END; WriteString[", Password: "L]; UNTIL (c←IODefs.ReadChar[])=CR OR c=SP OR c=ESC DO WriteChar['*]; String.AppendChar[password,c]; ENDLOOP; WriteChar[CR]; StringDefs.MesaToBcplString[user,OsStaticDefs.OsStatics.UserName]; StringDefs.MesaToBcplString[password,OsStaticDefs.OsStatics.UserPassword]; STP.Login[STPuser,user,password]; END; CollectRestOfNewString: PROCEDURE [s: STRING, c: CHARACTER] = BEGIN input: StreamDefs.StreamHandle = IODefs.GetInputStream[]; Backup[s]; input.putback[input, c]; s.length ← 0; ReadID[s]; END; Backup: PROCEDURE [s: STRING] = BEGIN outputStream: StreamDefs.StreamHandle = IODefs.GetOutputStream[]; ds: StreamDefs.DisplayHandle ← WITH o: outputStream SELECT FROM Display => @o, ENDCASE => NIL; i: CARDINAL; IF ds = NIL THEN RETURN; FOR i DECREASING IN [0..s.length) DO ds.clearChar[ds,s[i]]; ENDLOOP; END; ReadCmdStream: PROCEDURE [stream: StreamDefs.StreamHandle, name, switches: STRING] RETURNS [BOOLEAN] = BEGIN activestring: STRING ← name; c: CHARACTER; i: CARDINAL; name.length ← 0; switches.length ← 0; DO c ← stream.get[stream ! StreamDefs.StreamError => EXIT]; SELECT c FROM '/ => activestring ← switches; IODefs.SP => IF switches.length # 0 OR name.length # 0 THEN EXIT; IODefs.CR => EXIT; ENDCASE => String.AppendChar[activestring,c]; ENDLOOP; FOR i IN [0..switches.length) DO -- convert all to lower case IF (c←switches[i]) IN ['A..'Z] THEN switches[i] ← c + ('a-'A); ENDLOOP; RETURN[switches.length # 0 OR name.length # 0]; END; DoCompare: PROCEDURE = BEGIN oldStream,newStream: Stream.Handle ← NIL; oldSize, newSize: CARDINAL; cmdstream: StreamDefs.StreamHandle = StreamDefs.NewByteStream["Com.cm",SegmentDefs.Read]; [] ← ReadCmdStream[cmdstream,name,switches]; -- image [] ← ReadCmdStream[cmdstream,name,switches]; -- host InitSTP[name]; WHILE ReadCmdStream[cmdstream,name,switches] DO BEGIN ENABLE BEGIN STP.Error => {SELECT code FROM illegalUserName, illegalUserPassword => BEGIN WriteLine["Incorrect user/password."L]; Login[]; RETRY; END; ENDCASE =>{WriteString["Error: "L]; WriteError[error]}; LOOP}; Config => {WriteLine["Bound configuration; not a module."L]; ExhaustStream[newStream, buffer2]; Stream.Delete[newStream]; Stream.Delete[oldStream]; LOOP}; NoCode => {WriteLine["No code! Interface definition?"L]; ExhaustStream[newStream, buffer2]; Stream.Delete[newStream]; Stream.Delete[oldStream]; LOOP} END; IF switches.length > 0 THEN ProcessSwitches[] ELSE BEGIN WriteChar[CR]; WriteChar[CR]; ForceBCDExtension[name]; WriteString["Comparing file: "]; WriteString[name]; WriteString["..."]; newStream ← STP.CreateRemoteStream[STPuser,name, read]; WriteChar[CR]; CheckStream[newStream]; TrimFileName[name]; oldStream ← STP.CreateFileStream[name,read]; BEGIN ENABLE STP.Error => {WriteString["Error on remote file: "L]; WriteError[error]; ExhaustStream[newStream, buffer2]; Stream.Delete[newStream]; LOOP}; [newSize,version] ← FindCode[newStream, buffer2]; WriteString["Remote version"];PrintVersion[version]; WriteChar[CR]; END; BEGIN ENABLE STP.Error => {WriteString["Error on local file: "L]; WriteError[error]; ExhaustStream[newStream, buffer2]; Stream.Delete[newStream]; Stream.Delete[oldStream]; LOOP}; [oldSize,version] ← FindCode[oldStream, buffer1]; WriteString["Local version"]; PrintVersion[version]; WriteChar[CR]; END; CompareStreams[oldStream,newStream,oldSize,newSize !STP.Error => CONTINUE]; Stream.Delete[oldStream]; ExhaustStream[newStream, buffer2]; Stream.Delete[newStream]; END; END; ENDLOOP; Storage.FreeWords[buffer1]; --give buffers back Storage.FreeWords[buffer2]; Storage.FreeWords[header]; STP.Close[STPuser !STP.Error => CONTINUE]; STPuser ← STP.Destroy[STPuser]; END; WriteError: PROCEDURE [s: STRING] = BEGIN differences ← TRUE; WriteLine[s]; END; -- main program time:STRING ← [18]; Time.Append[time,Time.Unpack[0]]; ImageDefs.MakeImage["CodeCompare.image."]; WriteString["Alto/Mesa Code Compare 6.0 of "]; time.length ← time.length - 3; WriteLine[time]; WriteChar[CR]; time.length ← 0; Time.Append[time,Time.Unpack[0]]; WriteLine[time]; WriteChar[CR]; DoCompare[! STP.Error => BEGIN WriteString["Type any character to exit."]; [] ← ReadChar[]; ImageDefs.AbortMesa[]; END]; WriteChar[CR]; IF differences THEN BEGIN WriteString["Differences noted. Type any character to exit."]; [] ← ReadChar[]; END ELSE WriteString["No differences detected."]; ImageDefs.StopMesa[]; END.