-- file: CodeCompare.mesa
-- Edited by: Loretta, 24-Feb-81 18:45:50
-- Edited by: Satterthwaite, November 26, 1985 1:01:40 pm PST
DIRECTORY
Basics: TYPE USING [bytesPerWord],
BasicTime: TYPE USING [FromNSTime],
BcdDefs: TYPE USING [Base, BCD, BcdBase, MTHandle, MTRecord, SGHandle, VersionStamp],
Commander: TYPE USING [CommandProc, Handle, Register],
Convert: TYPE USING [IntFromRope],
FS: TYPE USING [Error, StreamOpen],
IO: TYPE USING [
STREAM, card, Close, Error, GetChar, Put, PutChar, PutRope, rope, time, UnsafeGetBlock],
Loader: TYPE USING [BCDBuildTime],
Rope: TYPE USING [ROPE, Cat, Fetch, Length, Substr],
VM: TYPE USING [
Interval, AddressForPageNumber, Allocate, Free, PagesForWords, wordsPerPage];
CodeCompare: CEDAR PROGRAM
IMPORTS BasicTime, Commander, Convert, FS, IO, Loader, Rope, VM = {
bytesPerWord: CARDINAL = Basics.bytesPerWord;
bytesPerPage: CARDINAL = bytesPerWord*VM.wordsPerPage;
MaxDiffs: CARDINAL ← 20;
bufferPages: NAT = 20;
bufferSize: CARDINAL = bufferPages*VM.wordsPerPage;
block1, block2: VM.Interval;
hBlock: VM.Interval;
-- Utilities
CompareStreams: PROC[
log, stream1, stream2: IO.STREAM, length1, length2: CARDINAL]
RETURNS[BOOL] = TRUSTED {
buffer1: LONG POINTER = VM.AddressForPageNumber[block1.page];
buffer2: LONG POINTER = VM.AddressForPageNumber[block2.page];
bufcount1, bufcount2: CARDINAL ← 0;
code1, code2: LONG POINTER TO ARRAY [0..bufferSize) OF WORD;
BuffersFilled: PROC[f1, f2: IO.STREAM, l1, l2: CARDINAL] RETURNS[BOOL] = TRUSTED {
IF MIN[l1, l2] = 0 THEN RETURN[FALSE];
SELECT bufcount1 FROM
= bufcount2 => {
code1 ← buffer1;
code2 ← buffer2;
bufcount1 ← f1.UnsafeGetBlock[[base~buffer1, startIndex~0, count~l1*bytesPerWord]];
bufcount2 ← f2.UnsafeGetBlock[[base~buffer2, startIndex~0, count~l2*bytesPerWord]];
bufcount1 ← bufcount1/bytesPerWord;
bufcount2 ← bufcount2/bytesPerWord;
RETURN[MIN[bufcount1, bufcount2] # 0]};
> bufcount2 => {
code1 ← buffer1;
code2 ← code2+bufcount1; --change array origin
bufcount2 ← bufcount2 - bufcount1;
bufcount1 ← f1.UnsafeGetBlock[[base~buffer1, startIndex~0, count~l1*bytesPerWord]];
bufcount1 ← bufcount1/bytesPerWord;
RETURN[bufcount1 # 0]};
< bufcount2 => {
code1 ← code1+bufcount2; --change array origin
code2 ← buffer2;
bufcount1 ← bufcount1 - bufcount2;
bufcount2 ← f2.UnsafeGetBlock[[base~buffer2, startIndex~0, count~l2*bytesPerWord]];
bufcount2 ← bufcount2/bytesPerWord;
RETURN[bufcount2 # 0]};
ENDCASE;
RETURN[FALSE]};
PutOctal: PROC[n: CARDINAL, columns: NAT, last: BOOL←TRUE] = CHECKED {
IF columns > 1 THEN PutOctal[n/8, columns-1, FALSE];
log.PutChar[IF ~last AND n=0 THEN ' ELSE VAL['0.ORD + (n MOD 8)]]};
difcount: NAT ← 0;
WHILE BuffersFilled[stream1, stream2, length1, length2] DO
length1 ← length1 - bufcount1; length2 ← length2 - bufcount2;
FOR i: CARDINAL IN [0..MIN[bufcount1, bufcount2]) DO
IF code1[i] # code2[i] THEN {
w: RECORD[left, right: [0..0FFh]];
PutOctal[i, 8];
PutOctal[code1[i], 8];
log.PutChar['[];
w ← LOOPHOLE[code1[i]];
PutOctal[w.left, 3]; log.PutChar[',]; PutOctal[w.right, 3];
log.PutChar[']];
PutOctal[code2[i], 8];
log.PutChar['[];
w ← LOOPHOLE[code2[i]];
PutOctal[w.left, 3]; log.PutChar[',]; PutOctal[w.right, 3];
log.PutChar[']];
log.PutChar['\n];
IF difcount = MaxDiffs THEN {
log.PutRope["Too many differences encountered. Quitting.\n"];
GOTO quit};
difcount ← difcount + 1};
ENDLOOP;
REPEAT quit => NULL;
ENDLOOP;
IF difcount = MaxDiffs THEN log.PutRope["More than "];
log.Put[IO.card[difcount], IO.rope[" differences\n"]];
RETURN[difcount = 0]};
Config: SIGNAL = CODE;
NoCode: SIGNAL = CODE;
FindCode: PROC[stream: IO.STREAM, block: VM.Interval] RETURNS[
codeCount: CARDINAL, dateStamp, sourceStamp: BcdDefs.VersionStamp] = TRUSTED {
-- positions stream to start of code
-- FindCode returns size in words (codeCount)
OPEN BcdDefs;
ENABLE
UNWIND => {NULL};
buf: LONG POINTER ← VM.AddressForPageNumber[block.page];
header: BcdBase = LOOPHOLE[VM.AddressForPageNumber[hBlock.page]];
streamPos: INT;
sgPointer: BcdDefs.Base;
mtPos, sgPos: CARDINAL;
codeBase: CARDINAL;
mOffset: CARDINAL;
mth: BcdDefs.MTHandle;
sgh: BcdDefs.SGHandle;
AdvanceStream: PROC [newPos: INT] = TRUSTED {
-- positions stream to byte position newPos
incr: INT;
WHILE newPos > streamPos DO
incr ← stream.UnsafeGetBlock[block~[buf, 0, MIN[bufferSize, newPos-streamPos]]];
streamPos ← streamPos + incr;
ENDLOOP
};
streamPos ← stream.UnsafeGetBlock[[LOOPHOLE[header], 0, BCD.SIZE*bytesPerWord]];
-- 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;
sourceStamp ← header.sourceVersion;
IF mtPos < sgPos THEN {
-- read module table first, record fields, then overwrite with segment table
AdvanceStream[mtPos];
mth ← LOOPHOLE[buf, BcdDefs.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, BcdDefs.SGHandle];
AdvanceStream[sgPos + LOOPHOLE[header.sgLimit,CARDINAL]*bytesPerWord]}
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
AdvanceStream[sgPos];
sgPointer ← LOOPHOLE[buf,BcdDefs.Base];
AdvanceStream[sgPos + LOOPHOLE[header.sgLimit,CARDINAL]*bytesPerWord];
buf ← LOOPHOLE[LOOPHOLE[buf,LONG CARDINAL] +
LOOPHOLE[header.sgLimit,CARDINAL],LONG POINTER];
AdvanceStream[mtPos];
mth ← LOOPHOLE[buf, BcdDefs.MTHandle];
AdvanceStream[mtPos + LOOPHOLE[header.mtLimit,CARDINAL]*bytesPerWord];
sgh ← @sgPointer[mth.code.sgi];
mOffset ← mth.code.offset;
codeCount ← mth.code.length};
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};
ForceBCDExtension: PROC[name: Rope.ROPE] RETURNS[Rope.ROPE] = {
root: Rope.ROPE;
FOR i: INT IN [0..name.Length) DO
IF name.Fetch[i] = '. THEN {root ← name.Substr[0, i]; EXIT};
REPEAT
FINISHED => root ← name;
ENDLOOP;
RETURN[root.Cat[".bcd"]]};
DoCompares: PROC[commands: Rope.ROPE, log: IO.STREAM] RETURNS[BOOL] = {
differences: BOOL ← FALSE;
name, remoteName: Rope.ROPE;
PrintDirectories: PROC = {
log.PutRope["\n* Local directory: "]; log.PutRope[localDir];
log.PutRope["\n* Remote directory: "]; log.PutRope[remoteDir];
log.PutChar['\n]};
PrintVersion: PROC[stamp: BcdDefs.VersionStamp] = {
stampWords: CARDINAL = BcdDefs.VersionStamp.SIZE;
str: PACKED ARRAY [0..4*stampWords) OF [0..16) = LOOPHOLE[stamp];
digit: PACKED ARRAY [0..15] OF CHAR =
['0, '1, '2, '3, '4, '5, '6, '7, '8, '9, 'a, 'b, 'c, 'd, 'e, 'f];
FOR i: CARDINAL IN [0..4*stampWords) DO
log.PutChar[digit[str[i]]] ENDLOOP
};
ProcessSwitches: PROC[switches: Rope.ROPE] = {
IF switches.Length > 1 THEN
SELECT switches.Fetch[1] FROM
'c,'C => {
lName, rName: Rope.ROPE;
[lName, rName] ← GetToken[];
IF lName.Length # 0 THEN localDir ← lName;
IF rName.Length # 0 THEN remoteDir ← rName;
PrintDirectories[]};
'm,'M => MaxDiffs ← Convert.IntFromRope[GetToken[].token];
ENDCASE;
};
TrimFileName: PROC[name: Rope.ROPE] RETURNS[Rope.ROPE] = {
first: INT ← 0;
IF name.Fetch[0] = '< THEN {
i: INT ← name.Length - 1;
WHILE i > 0 DO
IF name.Fetch[i] = '> THEN {
i ← i+1;
RETURN[name.Substr[i, name.Length-i]]};
i ← i - 1;
ENDLOOP;
RETURN[NIL]};
RETURN[name]};
cI: INT ← 0;
cLength: INT = commands.Length;
GetToken: PROC RETURNS[token, altToken: Rope.ROPE←NIL] = {
colon: BOOL ← FALSE;
start: INT ← cI;
c: CHAR;
Get: PROC RETURNS[c: CHAR] = INLINE {
IF cI >= cLength THEN c ← '\000
ELSE {c ← commands.Fetch[cI]; cI ← cI+1};
RETURN};
WHILE (c ← Get[]) # '\000 DO
SELECT c FROM
' , '\n =>
IF cI = start+1 THEN start ← cI
ELSE {
IF colon THEN altToken ← commands.Substr[start, cI-start-1]
ELSE token ← commands.Substr[start, cI-start-1];
RETURN};
': => {
token ← commands.Substr[start, cI-start-1];
colon ← TRUE; start ← cI};
ENDCASE => NULL;
ENDLOOP;
RETURN};
WriteError: PROC[s: Rope.ROPE] = {
differences ← TRUE; log.PutRope[s]; log.PutChar['\n]};
localDir, remoteDir: Rope.ROPE ← NIL;
oldStream, newStream: IO.STREAM ← NIL;
oldSize, newSize: CARDINAL;
block1 ← VM.Allocate[bufferPages];
block2 ← VM.Allocate[bufferPages];
hBlock ← VM.Allocate[VM.PagesForWords[BcdDefs.BCD.SIZE]];
[localDir, remoteDir] ← GetToken[];
IF remoteDir.Length = 0 THEN remoteDir ← localDir;
PrintDirectories[];
DO
BEGIN
ENABLE {
UNWIND => TRUSTED { --give buffers back
VM.Free[block1]; VM.Free[block2]; VM.Free[hBlock]};
Config => {
log.PutRope["Bound configuration; not a module."];
newStream.Close[]; oldStream.Close[];
LOOP};
NoCode => {
log.PutRope["No code!"];
newStream.Close[]; oldStream.Close[];
LOOP}
};
-- IF TTY.UserAbort[] THEN {log.PutRope[" ...aborted."]; TTY.ResetUserAbort[]; EXIT};
[name, remoteName] ← GetToken[];
IF name.Length = 0 THEN EXIT;
IF name.Fetch[1] = '- THEN ProcessSwitches[name]
ELSE {
version, sourceVersion: BcdDefs.VersionStamp;
log.PutChar['\n];
name ← ForceBCDExtension[TrimFileName[name]];
log.PutRope["Comparing "]; log.PutRope[name];
IF remoteName.Length # 0 THEN {
remoteName ← ForceBCDExtension[TrimFileName[remoteName]];
log.PutRope[" to "]; log.PutRope[remoteName]}
ELSE remoteName ← name;
log.PutRope["... "];
oldStream ← FS.StreamOpen[localDir.Cat[name]
! FS.Error => {
IF error.group # $bug THEN {
log.PutRope["No local file.\n"]; GOTO problems}}];
newStream ← FS.StreamOpen[remoteDir.Cat[remoteName]
! FS.Error => {
oldStream.Close[];
IF error.group # $bug THEN {
log.PutRope["No remote file.\n"]; GOTO problems}}];
log.PutChar['\n];
BEGIN
ENABLE
IO.Error => {
log.PutRope["Error on local file: "]; log.PutRope[name]; log.PutChar['\n];
newStream.Close[]; oldStream.Close[];
GOTO problems};
[oldSize, version, sourceVersion] ← FindCode[oldStream, block1];
log.PutRope["Local: "]; PrintVersion[version];
log.Put[IO.rope[" (from "], IO.time[BasicTime.FromNSTime[sourceVersion.time]]];
log.PutRope[", "];
log.Put[IO.card[oldSize], IO.rope[" words)\n"]];
END;
BEGIN
ENABLE
IO.Error => {
log.PutRope["Error on remote file: "]; log.PutRope[remoteName]; log.PutChar['\n];
newStream.Close[]; oldStream.Close[];
GOTO problems};
[newSize, version, sourceVersion] ← FindCode[newStream, block2];
log.PutRope["Remote: "]; PrintVersion[version];
log.Put[IO.rope[" (from "], IO.time[BasicTime.FromNSTime[sourceVersion.time]]];
log.PutRope[", "];
log.Put[IO.card[newSize], IO.rope[" words)\n"]];
END;
BEGIN
ENABLE IO.Error => {GOTO problems};
IF ~CompareStreams[log, oldStream,newStream, oldSize,newSize] THEN
differences ← TRUE;
END;
oldStream.Close[]; newStream.Close[]};
EXITS
problems => differences ← TRUE;
END;
ENDLOOP;
--give buffers back
TRUSTED {VM.Free[block1]; VM.Free[block2]; VM.Free[hBlock]};
RETURN[differences]};
Compare: Commander.CommandProc = {
differences: BOOL;
(cmd.out).Put[IO.rope["Cedar 6.0 Code Compare of "], IO.time[Loader.BCDBuildTime[]]];
(cmd.out).PutChar['\n];
differences ← DoCompares[cmd.commandLine, cmd.out
! IO.Error => {differences ← TRUE; CONTINUE}];
(cmd.out).PutChar['\n];
IF differences THEN {
(cmd.out).PutRope["Differences or problems noted. Type any character to exit.\n"];
[] ← (cmd.in).GetChar[]}
ELSE (cmd.out).PutRope["No differences detected.\n"]};
Init: PROC = {Commander.Register["CodeCompare", Compare]};
Init[];
}.