-- HexImpl6.mesa
-- Last Modified: Stewart  January 7, 1982  11:22 AM
-- Converts an Intel absolute loader format file into
--Intel hexadecimal absolute loader format.

DIRECTORY
  ComParse USING [Close, FreeString, Get, Open],
  Inline,
  IntelHex6,
  StreamDefs,
  WF;

HexImpl6: PROGRAM
IMPORTS ComParse, Inline, IntelHex6, StreamDefs, WF =
  BEGIN
  iName: STRING ← NIL;
  oName: STRING ← NIL;
  switches: STRING ← NIL;
  iS: StreamDefs.DiskHandle ← NIL;
  oS: StreamDefs.DiskHandle ← NIL;
  Mode: TYPE = {both, even, odd};
  mode: Mode;

  myIStream: IntelHex6.IStream ← [
    endof: myEndOf,
    get: myGetByte,
    GetPosition: myGetPosition,
    SetPosition: mySetPosition
    ];

  myOStream: IntelHex6.OStream ← [
    PutEndRecord: myPutEndRecord,
    PutStartRecord: myPutStartRecord,
    DataByte: myDataByte
    ];

  BitOp: TYPE = PROC [a, b: UNSPECIFIED] RETURNS [UNSPECIFIED];

  And: BitOp = INLINE { RETURN[Inline.BITAND[a,b]]; };
  Shift: BitOp = INLINE { RETURN[Inline.BITSHIFT[a,b]]; };

  WordOp: TYPE = PROC [a: UNSPECIFIED] RETURNS [UNSPECIFIED];

  Low: WordOp = INLINE { RETURN[Inline.LowHalf[a]]; };
  High: WordOp = INLINE { RETURN[Inline.HighHalf[a]]; };

  -- sum of bytes, contribution to checksum
  WordCk: PROC [c: CARDINAL] RETURNS [CARDINAL] = {
    v: Inline.BytePair ← LOOPHOLE[c];
    RETURN [v.low+v.high];
    };

  -- The checksum is the value making sum of checked bytes equal zero.
  CkSum: PROC [c: CARDINAL] RETURNS [CARDINAL] = {
    v: INTEGER ← And[c, 377B];
    v ← -v;
    v ← And[v, 377B];
    RETURN [LOOPHOLE[v, CARDINAL]];
    };

  -- Write an end record.
  myPutEndRecord: IntelHex6.PutEndRecordProc = {
    DataFlush[];
    WF.FWF0[oS, ":00000001FF*n"L];
    };

  -- Write a start record.
  myPutStartRecord: IntelHex6.PutStartRecordProc = {
    ck: CARDINAL;
    DataFlush[];
    ck ← CkSum[4+3+WordCk[frame]+WordCk[bytepos]];
    WF.FWF3[oS, ":04000003%04x%04x%02x*n"L, frame, bytepos, ck];
    };

  -- Write an extended address record.
  PutAddress: PROC [frame: CARDINAL] = {
    ck: CARDINAL ← CkSum[2+2+WordCk[frame]];
    WF.FWF2[oS, ":02000002%04x%02x*n"L, frame, ck];
    };

  InitLocalStorage: PROC = {
    gfirst ← gda ← LAST[LONG CARDINAL];
    gptr ← oldframe ← LAST[CARDINAL];
    frameinit ← FALSE;
    };

  -- Write a data record after every byte filling a paragraph
  --Write a new extended address record first thing and
  --thereafter when the bank changes.

  -- global variables for data address
  -- gfirst is the next address not yet written out
  -- gda is the expected next byte arriving from DataByte
  -- gptr is pointer into cache
  -- oldframe is the frame of the last extended address record
  -- frameinit is a first-time through flag
  gfirst, gda: LONG CARDINAL;
  gptr, oldframe: CARDINAL;
  gdata: ARRAY [0..16) OF CARDINAL;
  frameinit: BOOLEAN;

  NewDataAddress: PROC [addr: LONG CARDINAL] = {
    gptr ← 0;
    gfirst ← gda ← addr;
    IF NOT frameinit OR oldframe#High[addr] THEN {
      PutAddress[frame: High[addr]];
      oldframe ← High[addr];
      frameinit ← TRUE;
      };
    };

  myDataByte: IntelHex6.DataByteProc = {
    SELECT mode FROM
      even => {
        IF And[Low[adr], 1]  # 0 THEN RETURN;
        adr ← adr/2;  -- right shift
        };
      odd => {
        IF And[Low[adr], 1]  = 0 THEN RETURN;
        adr ← adr/2;  -- right shift
        };
      ENDCASE;
    IF NOT frameinit THEN NewDataAddress[adr];
    IF adr#gda OR High[adr]#oldframe THEN {
      DataFlush[];
      NewDataAddress[adr];
      };
    gdata[gptr] ← d;
    gptr ← gptr+1;
    gda ← adr+1;
    IF And[Low[gda], 17B] = 0 THEN DataFlush[];
    };

  DataFlush: PROC = {
    outChecksum: CARDINAL ← 0;
    IF gptr=0 THEN RETURN;
    WF.FWF2[oS, ":%02x%04x00"L, gptr, Low[gfirst]];
    outChecksum ← gptr + WordCk[Low[gfirst]];
    FOR i: CARDINAL IN [0..gptr) DO
      WF.FWF1[oS, "%02x"L, gdata[i]];
      outChecksum ← outChecksum + gdata[i];
      ENDLOOP;
    WF.FWF1[oS, "%02x*n"L, CkSum[outChecksum]];
    gptr ← 0;
    gfirst ← gda;
    };

  myEndOf: IntelHex6.EndOfProc = { RETURN[iS.endof[iS]]; };
  myGetByte: IntelHex6.GetByteProc = { RETURN[iS.get[iS]]; };
  myGetPosition: IntelHex6.GetPositionProc = { RETURN[StreamDefs.GetPosition[iS]]; };
  mySetPosition: IntelHex6.SetPositionProc = { StreamDefs.SetPosition[iS, p]; };

  myPutC: IntelHex6.PutCharProc = { WF.WFC[c]; };
  
  -- Mainline code

  {
  ComParse.Open[];
  iName ← ComParse.Get[];
  IF iName = NIL THEN {
    WF.WF0["Usage: Hex filename outputfilename*n"L];
    GOTO Cleanup;
    };
  oName ← ComParse.Get[];
  IF oName=NIL THEN {
    WF.WF0["Usage: Hex filename outputfilename*n"L];
    GOTO Cleanup;
    };
  switches ← ComParse.Get[];
  mode ← both;
  IF switches = NIL THEN mode ← both
  ELSE {
    FOR i: CARDINAL IN [0..switches.length) DO
      SELECT switches[i] FROM
        'e => { mode ← even; WF.WF0[" even mode*n"]; };
        'o => { mode ← odd; WF.WF0[" odd mode*n"]; };
        'b => { mode ← both; WF.WF0[" both mode*n"]; };
        ENDCASE;
      ENDLOOP;
    };
  iS ← StreamDefs.NewByteStream[iName, StreamDefs.Read !
    StreamDefs.FileNameError => {
      WF.WF1["Hex %s: no such file*n"L, iName];
      GOTO Cleanup;
      };];
  oS ← StreamDefs.NewByteStream[oName, StreamDefs.Write + StreamDefs.Append];
  iS.reset[iS];
  oS.reset[oS];
  InitLocalStorage[];
  IF NOT IntelHex6.ProcessFile[in: myIStream, out: myOStream, errs: myPutC] THEN WF.WF0["  Failed*n"];
  GOTO Cleanup;
  EXITS
  Cleanup => {
    ComParse.Close[];
    IF iS#NIL THEN iS.destroy[iS];
    IF oS#NIL THEN oS.destroy[oS];
    IF oName#NIL AND oName#iName THEN ComParse.FreeString[oName];
    IF iName#NIL THEN ComParse.FreeString[iName];
    };
  };

  END.
October 4, 1981  3:06 PM, LStewart; Created
December 23, 1981  3:31 PM, LStewart; Suppress duplicate address records
 2-Jan-82 11:45:27, LStewart; Use IntelHex6
January 7, 1982  11:22 AM, LStewart; Fewer extended address records