-- MakeLoaderFile.mesa
-- last edited:
--    January 9, 1984  4:46 PM by Taft
--   HGM on: October 10, 1980  2:49 AM

-- Make D0 boot files from .mb files

DIRECTORY
  AltoFileDefs USING [CFA],
  ImageDefs USING [MakeImage, StopMesa],
  InlineDefs USING [BITAND, BITNOT, BITOR, BITSHIFT, BITXOR, LowHalf, HighHalf], 
  IODefs USING [
    CR, ReadChar, ReadID, ReadLine, ReadOctal, WriteDecimal,
    WriteChar, WriteLine, WriteOctal, WriteString],
  MiscDefs USING [CommandLineCFA],
  SegmentDefs USING [
    Append, FileHandle, FileNameError, GetFileTimes, InsertFile,
    InvalidFP, Read, Write],
  StreamDefs USING [
    CreateByteStream, DiskHandle, GetIndex, JumpToFA, ModifyIndex,
    NewByteStream, NewWordStream, SetIndex, StreamError, WriteBlock],
  String USING [
    AppendChar, AppendString, EquivalentString, LowerCase, StringToOctal,
    WordsForString],
  Storage USING [Node],
  TimeDefs USING [PackedTime];

MakeLoaderFile: PROGRAM
  IMPORTS ImageDefs, InlineDefs, IODefs, MiscDefs, SegmentDefs,
    StreamDefs, String, Storage =
BEGIN

-- Central to this program is the array IMem, a model of the instruction memory.
-- Commands exist to load IMem from .mb format files, and
-- to dump IMem to .bt and .sb format files.
IMem: POINTER TO ARRAY IMemRange OF IMemCell;
IMemRange: TYPE = [0..4096);
IMemCell: TYPE = RECORD [first, second, third: WORD];
nullIMemCell: IMemCell = [0, 1, 0]; -- odd parity
PackedInst: TYPE = MACHINE DEPENDENT RECORD [SELECT OVERLAID * FROM
  inst => [addr: [0..4095], word2: [0..15], word0: WORD, word1: WORD],
  end => [addr: [0..4095], fill: [0..15], checkSum: WORD, startLoc: WORD], -- addr=4095
  words => [a, b, c: WORD],
  ENDCASE];
miPerSector: CARDINAL = 256/SIZE[PackedInst]; -- number of microinstructions per sector
bpCount: CARDINAL ← 0; -- counts BPs encountered

ClearIMem: PROCEDURE =
  BEGIN
  IMem↑ ← ALL[nullIMemCell];
  END;

Word: TYPE = MACHINE DEPENDENT RECORD [SELECT OVERLAID * FROM
  bytes => [left, right: [0..255]],
  word => [word: WORD],
  ENDCASE];

LoadIMem: PROCEDURE =
  BEGIN OPEN InlineDefs;
  MicroIMemory: CARDINAL = 1;
  MicroRMemory: CARDINAL = 2;
  MicroMMemory: CARDINAL = 3;
  MicroZMemory: CARDINAL = 4;
  NoMemory: CARDINAL = 100;
  fun: ARRAY [0..16] OF WORD = [
    0,100000B,140000B,160000B,170000B,
    174000B,176000B,177000B,177400B,177600B,177700B,
    177740B,177760B,177770B,177774B,177776B,177777B];
  currentMemory: WORD ← NoMemory;
  type: Word;
  -- Interpret Micro/MicroD-style output file
  --   (see Appendix 3 of Micro section of D0 Microprogrammmer's Manual)
  DO
    type ← [word[GetWord[]]]; -- right byte is significant; left is don't care here
    SELECT type.right FROM
      0 => -- end of file
        EXIT;
      1 => -- data word for current location of current memory
        BEGIN
        IMemAddr, IMemAddrWord: WORD;
        temp: IMemCell;
        SkipWords[1]; -- source line number
        SELECT currentMemory FROM
          MicroRMemory, MicroMMemory, MicroZMemory => SkipWords[1]; -- data
          MicroIMemory =>
            BEGIN
            temp ← [GetWord[], BITAND[GetWord[], 177776B], GetWord[]];
            IMemAddrWord ← GetWord[];
            Parity[@temp];
            IMemAddr ← BITAND[7777B, IMemAddrWord];
            IF (BITAND[IMemAddrWord, 140000B]#0) THEN
              BEGIN
              bpCount ← bpCount+1;
              IODefs.WriteString["BREAKPOINT instr at loc "]; IODefs.WriteOctal[IMemAddr];
              IODefs.WriteLine[""]
              END;
            IMem[IMemAddr] ← temp
            END;
          ENDCASE => IODefs.WriteString[" unknownMemoryType"];
        END;
      2 => -- switch memories/location
        BEGIN
        currentMemory ← GetWord[];
        SkipWords[1]; -- currentLocation; overridden by address in type 1 format
        END;
      -- 3 => forward reference fixup
      4 => -- memory-symbolic name correlation
        BEGIN
        SkipWords[2]; -- memory #, width of memory
        SkipString[] -- symbolic name
        END;
      5 => -- address symbol definition
        BEGIN
        SkipWords[2]; -- memory #, value
        SkipString[] -- symbol name
        END;
      6 => -- reference to undefined symbol
        BEGIN
        SkipWords[3]; -- memory #, location, first bit,,last bit
        SkipString[] -- symbol name
        END;
      ENDCASE => IODefs.WriteString["UnexpectedMicroFormat"]
    ENDLOOP
  END;

Parity: PROCEDURE [t: POINTER TO IMemCell] =
  BEGIN OPEN InlineDefs;
  p: CARDINAL;
  t.second ← BITAND[t.second, 177776B];
  p ← BITXOR[t.first, t.second];
  p ← BITXOR[p, BITAND[t.third, 170000B]];
  p ← BITXOR[p, BITSHIFT[p, -8]];
  p ← BITXOR[p, BITSHIFT[p, -4]];
  p ← BITXOR[p, BITSHIFT[p, -2]];
  p ← BITNOT[BITXOR[p, BITSHIFT[p, -1]]];
  t.second ← BITOR[t.second, BITAND[p, 1]]
  END;

Format: TYPE = {soft, ether};  -- See MakeLoaderFile.memo for details

softVersionNumber: CARDINAL ← 0;
etherVersionNumber: CARDINAL ← 1;

DumpIMem: PROCEDURE [format: Format, startLoc: CARDINAL] =
  BEGIN OPEN InlineDefs;
  i: CARDINAL; x: IMemCell; px: PackedInst;
  endMarker: PackedInst ← [end[addr: 7777B, fill: 0, checkSum: , startLoc: startLoc]];
  checkSum: WORD ← 0; -- accumulator for soft format check sum
  FOR i IN IMemRange DO
    x ← IMem[i];
    IF x#nullIMemCell THEN
      BEGIN
      px ← [inst[addr: i, word2: BITSHIFT[x.third, -12], word0: x.first, word1: x.second]];
      checkSum ← checkSum + px.a + px.b + px.c;
      PutInst[format: format, inst: px, andFlush: FALSE]
      END
    ENDLOOP;
  -- Put out end marker and flush buffer (if appropriate)
  endMarker.checkSum ← 0-(checkSum + endMarker.a + endMarker.c);
  PutInst[format: format, inst: endMarker, andFlush: TRUE]
  END;


-- Input/output routines
-- GetX means read from stream
-- PutX means write to stream
-- InX means read from command file or keyboard
-- OutX means write to command file or display

-- Input stream
infile: STRING = [40];
is: StreamDefs.DiskHandle; -- a word stream

GetWord: PROCEDURE RETURNS [WORD] =
  INLINE BEGIN
  RETURN[is.get[is]]
  END;

SkipWords: PROCEDURE [count: CARDINAL] =
  BEGIN
  THROUGH [0..count) DO [] ← is.get[is] ENDLOOP
  END;

SkipString: PROCEDURE = -- skip Bcpl-style string
  BEGIN
  word: Word;
  DO
    word ← [word[is.get[is]]];
    IF word.left=0 OR word.right=0 THEN EXIT
    ENDLOOP
  END;

-- Output stream
outfile: STRING = [40];
os: StreamDefs.DiskHandle; -- a word stream
wordsWritten: CARDINAL ← 0;

PutWord: PROCEDURE [w: WORD] =
  INLINE BEGIN
  os.put[os, w];
  wordsWritten ← wordsWritten+1
  END;

PutBlock: PROCEDURE [address: POINTER, words: CARDINAL] =
  BEGIN
  [] ← StreamDefs.WriteBlock[os, address, words];
  wordsWritten ← wordsWritten+words
  END;

PutHeader: PROCEDURE [format: Format, name: STRING] =
  INLINE BEGIN
  SELECT format FROM
    soft => PutWord[softVersionNumber];
    ether =>
      BEGIN
      createTime: TimeDefs.PackedTime ←
        SegmentDefs.GetFileTimes[os.file].create;
      destMaxLength: CARDINAL ← name.length + name.length MOD 2;
      wordsForString: CARDINAL ← String.WordsForString[destMaxLength];
-- The first 5 words must agree with the format of an Alto boot file
      PutWord[etherVersionNumber];
      PutWord[0];  -- or else boot file will get reformated
      PutWord[0];
      PutWord[InlineDefs.HighHalf[createTime]];  -- BCPL format
      PutWord[InlineDefs.LowHalf[createTime]];
      PutWord[name.length];  -- length
      PutWord[destMaxLength];  -- maxlength
      PutBlock[@name.text, wordsForString-2];
-- We should probably put a version stamp and our version stamp in here
      PutPadding[];
      END;
    ENDCASE => ERROR
  END;

PutInst: PROCEDURE [format: Format, inst: PackedInst, andFlush: BOOLEAN] =
  BEGIN
  PutWord[inst.a]; PutWord[inst.b]; PutWord[inst.c];
  END;

PutTrailer: PROCEDURE [format: Format] =
  BEGIN
  SELECT format FROM
    ether => NULL;
    soft => PutPadding[];
    ENDCASE => ERROR
  END;

PutPadding: PROCEDURE = -- make sure file size is multiple of 512 bytes
  BEGIN
  wordsOnLastPage: CARDINAL = wordsWritten MOD 256;
  IF wordsOnLastPage~=0 THEN
    THROUGH [0..256-wordsOnLastPage) DO PutWord[0] ENDLOOP
  END;

-- Command stream
commandFile: BOOLEAN ← FALSE; -- FALSE => read from keyboard
isCommand: StreamDefs.DiskHandle; -- a byte stream (also used to read Com.cm)

CR: CHARACTER = 15C;
Space: CHARACTER = ' ;
Tab: CHARACTER = 11C;

tstr: STRING ← [200];
tstrLength: CARDINAL;
InChar:  PROCEDURE RETURNS [ch: CHARACTER] =
  BEGIN
  tstr.length ← 0;
  IF commandFile THEN
    BEGIN
    InLine[tstr];
    RETURN[ch ← tstr[0]]
    END
  ELSE ch ← IODefs.ReadChar[]
  END;

InLine: PROCEDURE [st: STRING] =
  BEGIN
  i: CARDINAL; ch: CHARACTER;
  IF commandFile THEN
    BEGIN
    st.length ← i ← 0;
    UNTIL (ch ← isCommand.get[isCommand])=CR DO
      String.AppendChar[st,ch];
      i ← i+1
      ENDLOOP;
    tstrLength ← st.length ← i;
    i ← 0; -- skip over comments
    UNTIL i=st.length OR st[i]=Space OR st[i]=Tab DO i ← i+1 ENDLOOP;
    st.length ← i
    END
  ELSE IODefs.ReadLine[st]
  END;

InString: PROCEDURE [st: STRING] =
  BEGIN
  i: CARDINAL; ch: CHARACTER;
  IF commandFile THEN
    BEGIN
    i ← 0;
    UNTIL (ch ← isCommand.get[isCommand])=CR DO
      IODefs.WriteChar[ch];
      st[i] ← ch;
      i ← i+1
      ENDLOOP;
    st.length ← i;
    i ← 0; -- skip over comments
    UNTIL i=st.length OR st[i]=Space OR st[i]=Tab DO i ← i+1 ENDLOOP;
    st.length ← i
    END
  ELSE IODefs.ReadID[st]
  END;

InOctal: PROCEDURE RETURNS [c: CARDINAL] =
  BEGIN
  i: CARDINAL ← 0; st: STRING ← [7]; ch: CHARACTER;
  IF commandFile THEN
    BEGIN
    UNTIL (ch ← LOOPHOLE[isCommand.get[isCommand]])=CR OR ch=Space DO
      IODefs.WriteChar[ch];
      st[i] ← ch;
      i ← i+1
      ENDLOOP;
    st.length ← i;
    c ← String.StringToOctal[st]
    END
  ELSE c ← IODefs.ReadOctal[]
  END;

InBoolean: PROCEDURE RETURNS [b: BOOLEAN] =
  BEGIN
  b ← String.LowerCase[InChar[]]='y;
  OutLine[IF b THEN "Yes" ELSE "No"]
  END;

InFileName: PROCEDURE [name, ext: STRING] =
  BEGIN
  i: CARDINAL;
  InString[name]; OutLine[""];
  FOR i DECREASING IN [0..name.length) DO
    IF name[i]='. THEN RETURN
    ENDLOOP;
  String.AppendString[to: name, from: ext]
  END;

OutString:  PROCEDURE [st:STRING] =
  BEGIN IF TRUE OR NOT commandFile THEN IODefs.WriteString[st] END;

OutLine: PROCEDURE [st:STRING] =
  BEGIN IF TRUE OR NOT commandFile THEN IODefs.WriteLine[st] END;

WriteCR: PROCEDURE =
  BEGIN IODefs.WriteChar[IODefs.CR] END;

Resynch: PROCEDURE =
  BEGIN
  IODefs.WriteString["Type any key to exit."]; [] ← IODefs.ReadChar[]
  END;

GetToken: PROCEDURE [stream: StreamDefs.DiskHandle, string: STRING] =
  BEGIN
  c: CHARACTER;
  string.length ← 0;
  DO
    IF (c←stream.get[stream ! StreamDefs.StreamError => EXIT]) <= Space THEN
      BEGIN IF string.length # 0 THEN EXIT END
	 ELSE String.AppendChar[string,c];
    ENDLOOP;
  END;

TestForMakeImage: PROCEDURE =
  -- makes image if command line says "/i"
  -- returns with isCommand set up to read commands
  BEGIN OPEN SegmentDefs, StreamDefs;
  cfa: POINTER TO AltoFileDefs.CFA ← MiscDefs.CommandLineCFA[];
  cfile: FileHandle ← InsertFile[@cfa.fp,Read];
  s: STRING ← [40];
  isCommand ← NIL;
  isCommand ← CreateByteStream[cfile,Read ! InvalidFP => CONTINUE];
  IF isCommand # NIL THEN
    BEGIN
    JumpToFA[isCommand,@cfa.fa];
    WHILE isCommand.get[isCommand
            ! StreamError => GOTO nocommands] <= Space DO
      NULL ENDLOOP;
    SetIndex[isCommand,ModifyIndex[GetIndex[isCommand],-1]];
    EXITS nocommands =>
      BEGIN JumpToFA[isCommand,@cfa.fa]; RETURN END;
    END;
  GetToken[isCommand, s];
  IF String.EquivalentString[s, "/i"L] THEN
    BEGIN
    isCommand.destroy[isCommand];
    ImageDefs.MakeImage["MakeLoaderFile.image"L];
    isCommand ← NewByteStream["Com.Cm"L, Read];
    GetToken[isCommand, s]; -- skip image file name
    END
  ELSE
    BEGIN
    JumpToFA[isCommand,@cfa.fa];
    END;
  END;


-- Main code

BEGIN
format: Format ← ether;
appendToCurrentFile: BOOLEAN ← FALSE;
str: STRING ← [40];

TestForMakeImage[]; -- sets up isCommand
IODefs.WriteLine["MakeLoaderFile of 21-Jun-82 15:51:55"];

-- Check for command file specified in Com.cm
GetToken[isCommand, str];
isCommand.destroy[isCommand];
IF str.length#0 THEN 
  BEGIN
  i: CARDINAL;
  FOR i IN [0..str.length) DO
    IF str[i] = '. THEN EXIT;
    REPEAT FINISHED => String.AppendString[str, ".mlf"];
    ENDLOOP;
  isCommand ← StreamDefs.NewByteStream[name: str, access: SegmentDefs.Read
    ! SegmentDefs.FileNameError => GOTO nofile];
  commandFile ← TRUE;
  IODefs.WriteString["Commands from "]; IODefs.WriteLine[str]
  EXITS nofile => NULL
  END;

-- Allocate space for IMem
IMem ← Storage.Node[SIZE[IMemCell]*(LAST[IMemRange]+1)];
ClearIMem[];

DO
  OutString["Command: "];
  SELECT String.LowerCase[InChar[]] FROM

    '* => BEGIN tstr.length ← tstrLength;  IODefs.WriteLine[tstr]; END; -- Comment

    'r =>
      BEGIN
      OutString["Read file: "];
      InFileName[infile, ".mb"];
      is ← StreamDefs.NewWordStream[name: infile, access: SegmentDefs.Read
        ! SegmentDefs.FileNameError => GOTO nofile];
      LoadIMem[];
      is.destroy[is]
      EXITS nofile =>
        BEGIN
        IODefs.WriteString["Can't find "];
        IODefs.WriteLine[infile];
        Resynch[];
        EXIT;
        END;
      END;

    'p =>
      BEGIN
      IODefs.WriteLine["Pad to the end of this page (for making initial boot files)"];
      PutPadding[];
      END;

    's =>
      BEGIN
      IODefs.WriteLine["(Old fashioned) Soft boot format"];
      format ← soft
      END;

    'w =>
      BEGIN
      startLoc: CARDINAL;
      OutString["Write"];

      IF NOT appendToCurrentFile THEN
        BEGIN
        trailer: STRING;
        SELECT format FROM
          soft =>  trailer ← ".sb";
          ether =>  trailer ← ".eb";
          ENDCASE => ERROR;
        OutString[" onto file: "];
        InFileName[outfile, trailer];
        os ← StreamDefs.NewWordStream[name: outfile, access: SegmentDefs.Write+SegmentDefs.Append];
        PutHeader[format, outfile]
        END
      ELSE OutLine[""];

      OutString["Starting location (octal) : "]; startLoc ← InOctal[]; OutLine[" "];

      OutLine["**If you want overlays, answer No to next question**"];
      OutString["Close output file afterwords? "]; appendToCurrentFile ← ~InBoolean[];

      DumpIMem[format, startLoc];

      IODefs.WriteString[outfile]; IODefs.WriteString[" contains "];
      IODefs.WriteDecimal[(wordsWritten+255)/256]; IODefs.WriteLine[" pages"];

      IF ~appendToCurrentFile THEN
        BEGIN PutTrailer[format]; os.destroy[os]; wordsWritten ← 0 END;

      OutString["Clear Control store? "]; IF InBoolean[] THEN ClearIMem[]
      END;

    'q =>
      BEGIN
      IODefs.WriteLine["Quit"];
      IF appendToCurrentFile THEN
        BEGIN
        IODefs.WriteLine["Error - last write didn't close output file"];
        Resynch[];
        END;
      EXIT
      END;
    ENDCASE => OutLine["Commands are: Read, Soft format, Pad to end of page, Write, Quit"]
  ENDLOOP
END;

IF commandFile THEN isCommand.destroy[isCommand];
ImageDefs.StopMesa[];

END.

LOG
Time: June 13, 1978	By: Hankins	Action: Created file
Time: October 25, 1978	By: Hankins	Action: ???
Time: January, 1978	By: Hankins	Action: ???
Time: March 12, 1979  4:21 PM	By: McJones	Action: Converted to Mesa 5; added soft booting
Time: May 2, 1979  5:07 PM	By: Johnsson	Action: run from bcd
Time: May 23, 1979  5:19 PM	By: Johnsson	Action: Breakpoints command
Time: June 4, 1979  9:42 AM	By: Johnsson	Action: fix BP vs. CheckSum
Time: December 30, 1979  4:43 PM	By: Murray	Action: ether format
Time: Aug 80	By: Murray	Action: add padding option
Time: September 23, 1980  7:30 PM	By: Murray	Action: cleanup+bugfixes
Time: September 23, 1980  7:30 PM	By: Murray	Action: Fixup BoundsCheck glitch in checksum, delete Clear and version commands
21-Jun-82 15:50:49  Taft  Add filename to header
 7-Jul-82 15:38:42  Taft  Make embedded date be same as file creation date
January 9, 1984  4:45 PM  Taft  Add PutBlock; fix bug in padding computation for header