File [ivy]<Satterthwaite>CodeCompare>CodeCompare.mesa!2

    -- 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[];

      }.