-- 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.