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